remotes/origin/PR-39
Mukhtar 10 years ago
commit 83e07bfd96
  1. 20
      archilance/util.py
  2. 73
      assets/css/main.css
  3. 2
      assets/js/chat.js
  4. 9
      assets/js/main.js
  5. 8
      chat/templates/chat_contractor.html
  6. 9
      chat/templates/chat_customer.html
  7. 32
      projects/forms.py
  8. 24
      projects/migrations/0014_auto_20160823_1912.py
  9. 29
      projects/migrations/0015_auto_20160824_1538.py
  10. 19
      projects/models.py
  11. 22
      projects/templates/customer_project_create.html
  12. 32
      projects/templates/customer_project_edit.html
  13. 151
      projects/templates/portfolio_detail.html
  14. 726
      projects/templates/project_detail.html
  15. 22
      projects/templates/project_filter.html
  16. 26
      projects/templatetags/project_tags.py
  17. 12
      projects/urls.py
  18. 255
      projects/views.py
  19. 8
      ratings/templates/templatetags/ratings_widget.html
  20. 37
      ratings/templates/templatetags/specializations_widget.html
  21. 18
      ratings/templatetags/specializtions_tags.py
  22. 8
      templates/partials/header.html
  23. 6
      update.sh
  24. 50
      users/templates/contractor_filter.html
  25. 63
      users/templates/contractor_office.html
  26. 15
      users/templates/contractor_profile.html
  27. 4
      users/templates/portfolio_create_form.html
  28. 45
      users/templates/worksell_create_form.html
  29. 1
      users/views.py
  30. 39
      wallets/views.py
  31. 15
      work_sell/templates/worksell_detail.html
  32. 5
      work_sell/templates/worksells_list.html

@ -1,9 +1,11 @@
from django.core import validators
from django.shortcuts import _get_queryset from django.shortcuts import _get_queryset
from django.utils import timezone from django.utils import timezone
from pprint import pprint, pformat from pprint import pprint, pformat
import natsort import natsort
import pydash as _; _.map = _.map_; _.filter = _.filter_ import pydash as _; _.map = _.map_; _.filter = _.filter_
import random import random
import string
def take(coll, n): def take(coll, n):
@ -89,3 +91,21 @@ def decap(s):
raise TypeError('String expected') raise TypeError('String expected')
return s[0].lower() + s[1:] if len(s) > 0 else s return s[0].lower() + s[1:] if len(s) > 0 else s
def random_ident(length=8):
return ''.join(
[random.choice(string.ascii_lowercase)] +
_.times(lambda x_: random.choice(string.ascii_letters + string.digits), length-1)
)
def validate_phone(text):
text = text.replace(' ', '').replace('-', '')
validate = validators.RegexValidator(
regex=r'^((\+7|8)(\(\d{3}\)|(\d{3}))\d{7})$',
message='Неверный номер телефона. Формат: +71112223344',
)
validate(text)

@ -1041,7 +1041,7 @@ footer:after {
background-position: 0 -26px; background-position: 0 -26px;
} }
.rightPro ul li:last-child:before { .rightPro ul li:nth-child(3):before {
height: 22px; height: 22px;
background: url('../img/cenaList.png') no-repeat left; background: url('../img/cenaList.png') no-repeat left;
background-size: cover; background-size: cover;
@ -5639,13 +5639,13 @@ input[type="radio"]:checked + span {
right: 3px; right: 3px;
top: 16px; top: 16px;
} }
#portfolio-add-form .textAreaBlock2 p{ #portfolio-add-form .textAreaBlock2 p, #worksell-add-form .textAreaBlock2 p{
font-size: 15px; font-size: 15px;
font-family: 'Arial-MT-Regular', sans-serif; font-family: 'Arial-MT-Regular', sans-serif;
font-weight: bold; font-weight: bold;
color: #2c2c2c; color: #2c2c2c;
} }
#portfolio-add-form #text-new{ #portfolio-add-form #text-new, #worksell-add-form #text-new{
margin-top: 0; margin-top: 0;
} }
.textAreaBlock2 input{ .textAreaBlock2 input{
@ -5657,10 +5657,10 @@ input[type="radio"]:checked + span {
color: #9c9c9c; color: #9c9c9c;
font-family: 'Arial-MT-Regular', sans-serif; font-family: 'Arial-MT-Regular', sans-serif;
} }
#portfolio-add-form .polsF1 .btn{ #portfolio-add-form .polsF1 .btn, #worksell-add-form .polsF1 .btn{
border:1px solid #c2c2c2; border:1px solid #c2c2c2;
} }
#portfolio-add-form .bootstrap-select{ #portfolio-add-form .bootstrap-select, #worksell-add-form .bootstrap-select{
width: 100% !important; width: 100% !important;
} }
.textAreaBlock2 .add_file_to_port{ .textAreaBlock2 .add_file_to_port{
@ -5750,7 +5750,7 @@ input[type="radio"]:checked + span {
margin: 4px 0 4px 0; margin: 4px 0 4px 0;
} }
.birth_edit_dat .bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn){ .birth_edit_dat .bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn){
width: 190px; width: 190px !important;
} }
.btn-submit-link{ .btn-submit-link{
color: #333 !important; color: #333 !important;
@ -5758,4 +5758,65 @@ input[type="radio"]:checked + span {
border-color: #ccc !important; border-color: #ccc !important;
margin-bottom: 0 !important margin-bottom: 0 !important
} }
.infoProfile .dropdown-menu{
height: 316px;
}
.form-regestration{
padding: 20px 0 40px 0;
}
#resume-text-edit .modal-title, #fullfill-balance .modal-title, #withdraw-money .modal-title{
font-family: Arial, Verdana, Helvetica, sans-serif;
font-size: 16px;
text-transform: uppercase;
color: #2c2c2c;
font-weight: bold;
}
#withdraw-money .modal-body, #fullfill-balance .modal-body{
width: 100%;
display: table;
}
#withdraw-money .modal-body p, #fullfill-balance .modal-body p{
color: #2c2c2c;
font-family: Arial, Verdana, Helvetica, sans-serif;
font-weight: bold;
}
#withdraw-money .modal-footer .btn, #fullfill-balance .modal-footer .btn{
padding: 10px 34px;
border-radius: 40px;
font-family: 'pfdintextcomppro-regular', sans-serif;
font-size: 16px;
text-transform: uppercase;
letter-spacing: 2px;
}
:focus{
outline: none !important;
}
#withdraw-money .modal-footer .btn.btn-primary, #fullfill-balance .modal-footer .btn.btn-primary{
background-color: #000;
}
.modal-footer{
border-top: none;
}
#resume-text-edit .btn-submit-link{
float: right;
margin-top: 15px;
}
#resume-text-edit .searchF1{
padding: 0;
}
.list-summ-block{
min-height: 195px;
}
.select2-dropdown-open > a.select2-choice{
border: 1px solid #ff0029 !important;
}
.select2-display-none {
border: 1px solid #ff0029 !important;
}
.bootstrap-select.btn-group .dropdown-menu li a span.text {
font-size: 14px;
display: block;
text-transform: initial;
letter-spacing: normal;
}
/*end_new*/ /*end_new*/

@ -1,6 +1,6 @@
var SocketHandler = function () { var SocketHandler = function () {
domain = domain.replace(':' + port, ''); domain = domain.replace(':' + port, '');
var url = 'ws://' + domain + ':8888/chat/' + userId + '/'; var url = 'ws://' + domain + '/chat/' + userId + '/';
var sock = new WebSocket(url); var sock = new WebSocket(url);
var intervalId; var intervalId;
sock.onopen = function () { sock.onopen = function () {

@ -3,9 +3,14 @@ $(document).ready(function(){
$('.messd:first').addClass('mesAct'); $('.messd:first').addClass('mesAct');
$('.orderBlock:first').addClass('orAct'); $('.orderBlock:first').addClass('orAct');
if(!$.cookie('slideResVisible')) { if ($.cookie('slideResVisible')) {
$('.slideRes').slideDown(300);
$('.resButtonF1').css('transform','rotate(0deg)');
$('#extraFields').val('on')
} else {
$('.slideRes').slideUp(300); $('.slideRes').slideUp(300);
$('.resButtonF1').css('transform','rotate(180deg)'); $('.resButtonF1').css('transform','rotate(180deg)');
$('#extraFields').val('')
} }
$('.resButtonF1').click(function(e){ $('.resButtonF1').click(function(e){
@ -15,10 +20,12 @@ $(document).ready(function(){
$('.slideRes').slideUp(300); $('.slideRes').slideUp(300);
$(this).css('transform','rotate(180deg)'); $(this).css('transform','rotate(180deg)');
$.cookie('slideResVisible', '', {expires: new Date(new Date().getTime() + 300000)}) // 5 minutes $.cookie('slideResVisible', '', {expires: new Date(new Date().getTime() + 300000)}) // 5 minutes
$('#extraFields').val('')
} else { } else {
$('.slideRes').slideDown(300); $('.slideRes').slideDown(300);
$(this).css('transform','rotate(0deg)'); $(this).css('transform','rotate(0deg)');
$.cookie('slideResVisible', 'on', {expires: new Date(new Date().getTime() + 300000)}) $.cookie('slideResVisible', 'on', {expires: new Date(new Date().getTime() + 300000)})
$('#extraFields').val('on')
} }
}); });

@ -47,7 +47,13 @@
{% endthumbnail %} {% endthumbnail %}
</div> </div>
<p class="nameMess"> <p class="nameMess">
<div><a href="#" style="color:black;">{{ contact.username }}</a></div> {% if contact.is_contractor %}
{% url "users:contractor-profile" pk=contact.pk as contact_url %}
{% else %}
{% url "users:customer-profile-open-projects" pk=contact.pk as contact_url %}
{% endif %}
<div><a href="{{ contact_url }}" style="color:black;">{{ contact.username }}</a></div>
</p> </p>
<a href="#" class="conMess">Контакты</a> <a href="#" class="conMess">Контакты</a>

@ -41,7 +41,12 @@
{% endif %} {% endif %}
</div> </div>
<p class="nameMess"> <p class="nameMess">
<div><a href="#">{{ contact.username }}</a></div> {% if contact.is_contractor %}
{% url "users:contractor-profile" pk=contact.pk as contact_url %}
{% else %}
{% url "users:customer-profile-open-projects" pk=contact.pk as contact_url %}
{% endif %}
<div><a href="{{ contact_url }}">{{ contact.username }}</a></div>
</p> </p>
<a href="#" data-target="#userModal" data-toggle="modal" <a href="#" data-target="#userModal" data-toggle="modal"
@ -148,7 +153,7 @@
Обсуджение задания и условий выполнения работы. Подтверждение заказа исполнителем. Обсуджение задания и условий выполнения работы. Подтверждение заказа исполнителем.
</p> </p>
</div> </div>
<div id="order-stages" class="stepssBlock"></div> <div id="order-stages" class="stepssBlock"></div>
<div class="stepssBlock box-sizing disTab" id="reserveSpace" style="display:none;"> <div class="stepssBlock box-sizing disTab" id="reserveSpace" style="display:none;">

@ -6,6 +6,7 @@ from pprint import pprint, pformat
import itertools import itertools
from .models import Project, ProjectFile, Portfolio, Answer, AnswerMessage, Realty, PortfolioPhoto, Stage from .models import Project, ProjectFile, Portfolio, Answer, AnswerMessage, Realty, PortfolioPhoto, Stage
from archilance import util
from common.models import Location from common.models import Location
from specializations.models import Specialization from specializations.models import Specialization
from users.models import User, Team from users.models import User, Team
@ -117,12 +118,27 @@ class CustomerProjectEditForm(forms.ModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['realty'].empty_label = 'Создать новый' self.fields['realty'].empty_label = 'Создать новый'
self.fields['realty'].queryset = self.request.user.realties.all()
self.fields['budget'].required = False
self.fields['currency'].required = False
self.fields['term_type'].required = False
self.fields['specialization'].queryset = Specialization.objects.root_nodes()[0].get_descendants() self.fields['specialization'].queryset = Specialization.objects.root_nodes()[0].get_descendants()
# self.fields['specialization'].queryset = Specialization.objects # Migrate with this enabled # self.fields['specialization'].queryset = Specialization.objects # Migrate with this enabled
if self.instance.pk: if self.instance.pk:
self.fields['files'].queryset = self.instance.files self.fields['files'].queryset = self.instance.files
def clean(self):
cleaned_data = super().clean()
if not cleaned_data.get('budget_by_agreement'):
if not cleaned_data.get('budget'): raise forms.ValidationError({'budget': 'Это поле обязательно'})
if not cleaned_data.get('currency'): raise forms.ValidationError({'currency': 'Это поле обязательно'})
if not cleaned_data.get('term_type'): raise forms.ValidationError({'term_type': 'Это поле обязательно'})
return cleaned_data
class RealtyForm(forms.ModelForm): class RealtyForm(forms.ModelForm):
@ -145,8 +161,24 @@ class RealtyForm(forms.ModelForm):
self.request = kwargs.pop('request') self.request = kwargs.pop('request')
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['name'].required = False
self.fields['location'].queryset = Location.objects.root_nodes()[0].get_descendants() self.fields['location'].queryset = Location.objects.root_nodes()[0].get_descendants()
# self.fields['location'].queryset = Location.objects # Migrate with this enabled # self.fields['location'].queryset = Location.objects # Migrate with this enabled
def clean_name(self):
name = self.cleaned_data.get('name')
if not name:
if self.data.get('extra_fields'):
raise forms.ValidationError('Это поле обязательно')
else:
if not self.instance.pk:
name = self.instance.name
else:
name = 'Новый объект %s' % util.random_ident(length=20)
return name
class PortfolioForm(forms.ModelForm): class PortfolioForm(forms.ModelForm):

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-23 16:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('projects', '0013_auto_20160819_1735'),
]
operations = [
migrations.AlterModelOptions(
name='project',
options={'ordering': ('-created',), 'verbose_name': 'Проект', 'verbose_name_plural': 'Проекты'},
),
migrations.AddField(
model_name='answer',
name='rejected',
field=models.BooleanField(default=False),
),
]

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-24 12:38
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('projects', '0014_auto_20160823_1912'),
]
operations = [
migrations.AlterModelOptions(
name='portfolio',
options={'ordering': ['-created'], 'verbose_name': 'Портфолио', 'verbose_name_plural': 'Портфолио'},
),
migrations.RemoveField(
model_name='project',
name='rejected_answers_count',
),
migrations.AddField(
model_name='portfolio',
name='created',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

@ -78,7 +78,7 @@ class Project(models.Model, HitCountMixin):
('trashed', 'В корзине'), ('trashed', 'В корзине'),
('deleted', 'Удален'), ('deleted', 'Удален'),
) )
budget = models.DecimalField(max_digits=10, decimal_places=0) budget = models.DecimalField(max_digits=10, decimal_places=0)
budget_by_agreement = models.BooleanField(default=False) budget_by_agreement = models.BooleanField(default=False)
created = models.DateTimeField(default=timezone.now) created = models.DateTimeField(default=timezone.now)
@ -89,7 +89,6 @@ class Project(models.Model, HitCountMixin):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
price_and_term_required = models.BooleanField(default=False) price_and_term_required = models.BooleanField(default=False)
realty = models.ForeignKey(Realty, blank=True, null=True, related_name='projects') realty = models.ForeignKey(Realty, blank=True, null=True, related_name='projects')
rejected_answers_count = models.PositiveIntegerField(default=0)
specialization = TreeForeignKey(Specialization, related_name='projects') specialization = TreeForeignKey(Specialization, related_name='projects')
state = models.CharField(default='active', max_length=20, choices=STATES) state = models.CharField(default='active', max_length=20, choices=STATES)
term = models.IntegerField(default=0) term = models.IntegerField(default=0)
@ -134,6 +133,7 @@ class Answer(models.Model):
term = models.IntegerField(blank=True, null=True) term = models.IntegerField(blank=True, null=True)
term_type = models.CharField(max_length=10, choices=TERMS, blank=True, null=True) term_type = models.CharField(max_length=10, choices=TERMS, blank=True, null=True)
is_archive = models.BooleanField(default=False) is_archive = models.BooleanField(default=False)
rejected = models.BooleanField(default=False)
content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team')) content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team'))
object_id = models.IntegerField() object_id = models.IntegerField()
@ -278,11 +278,25 @@ class Portfolio(models.Model):
term_type = models.CharField(max_length=20, choices=TERMS, default='hour', null=True, blank=True) term_type = models.CharField(max_length=20, choices=TERMS, default='hour', null=True, blank=True)
user = models.ForeignKey(User, related_name='portfolios', null=True, blank=True) user = models.ForeignKey(User, related_name='portfolios', null=True, blank=True)
worksell = models.BooleanField(default=False) worksell = models.BooleanField(default=False)
created = models.DateTimeField(default=timezone.now)
def __str__(self): def __str__(self):
return self.name return self.name
def get_prev(self):
try:
return self.get_previous_by_created()
except self.DoesNotExist:
return None
def get_next(self):
try:
return self.get_next_by_created()
except self.DoesNotExist:
return None
class Meta: class Meta:
ordering = ['-created']
verbose_name = 'Портфолио' verbose_name = 'Портфолио'
verbose_name_plural = 'Портфолио' verbose_name_plural = 'Портфолио'
@ -291,6 +305,7 @@ class Portfolio(models.Model):
return photo and photo.img return photo and photo.img
class PortfolioPhoto(models.Model): class PortfolioPhoto(models.Model):
img = models.ImageField(upload_to='projects/portfolio') img = models.ImageField(upload_to='projects/portfolio')
portfolio = models.ForeignKey(Portfolio, related_name='photos') portfolio = models.ForeignKey(Portfolio, related_name='photos')

@ -14,6 +14,8 @@
<form action="{% url 'projects:customer-project-create' %}" method="POST" enctype="multipart/form-data" novalidate> <form action="{% url 'projects:customer-project-create' %}" method="POST" enctype="multipart/form-data" novalidate>
{% csrf_token %} {% csrf_token %}
<input type="hidden" id="extraFields" name="extra_fields" value="">
<div class="chatBlock new-rass new-rass2 disTab"> <div class="chatBlock new-rass new-rass2 disTab">
<div class="col-lg-9"> <div class="col-lg-9">
<p class="new-pp new-pp3">Формирование заказа</p> <p class="new-pp new-pp3">Формирование заказа</p>
@ -52,7 +54,7 @@
<p class="type-work">Тип работы <span style="color: red">{{ form.work_type.errors.as_text }}</span></p> <p class="type-work">Тип работы <span style="color: red">{{ form.work_type.errors.as_text }}</span></p>
<div class="mail-block type-work-inset"> <div class="mail-block type-work-inset">
{% for id, name in form.work_type.field.choices %} {% for id, text in form.work_type.field.choices %}
<div class="inset-mb"> <div class="inset-mb">
<label> <label>
<input <input
@ -64,7 +66,7 @@
<span></span> <span></span>
</label> </label>
<p>{{ name }}</p> <p>{{ text }}</p>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -198,6 +200,7 @@
<div class="borderS1"></div> <div class="borderS1"></div>
</div> </div>
</div> </div>
<div class="slideRes disTab activeSlide"> <div class="slideRes disTab activeSlide">
<div class="titleF1 disTab"> <div class="titleF1 disTab">
<div class="col-lg-3">Выбор объекта<br><span style="color: red">{{ form.realty.errors.as_text }}</span></div> <div class="col-lg-3">Выбор объекта<br><span style="color: red">{{ form.realty.errors.as_text }}</span></div>
@ -271,21 +274,12 @@
<p>Требуется допуск (СРО)</p> <p>Требуется допуск (СРО)</p>
</div> </div>
</div> </div>
{# {% if back %}<a href='{{ back }}'>Назад</a>{% endif %}#}
<div class="searchF1 polsF1 polsFF links-filter">
<input class="btn-submit-link" type="submit" value="Разместить проект">
</div>
</div> </div>
<div class="searchF1 polsF1 polsFF links-filter">
<input class="btn-submit-link" type="submit" value="Разместить проект">
</div>
</div> </div>
</div> </div>
</form> </form>

@ -13,7 +13,9 @@
<form action="{% url 'projects:customer-project-edit' pk=pk %}" method="POST" enctype="multipart/form-data" novalidate> <form action="{% url 'projects:customer-project-edit' pk=pk %}" method="POST" enctype="multipart/form-data" novalidate>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=pk %}"> <input type="hidden" name="next" value="{% url 'projects:detail' pk=pk %}">
<input type="hidden" id="extraFields" name="extra_fields" value="">
<div class="chatBlock new-rass new-rass2 disTab"> <div class="chatBlock new-rass new-rass2 disTab">
<div class="col-lg-9"> <div class="col-lg-9">
@ -92,22 +94,22 @@
</div> </div>
<div class="polsF1 disTab -spec-select-container"> <div class="polsF1 disTab -spec-select-container">
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-spec-select -spec-select-level-1" style="width: 100%"> <input type="hidden" class="-spec-select -spec-select-level-1" style="width: 100%">
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-spec-select -spec-select-level-2" style="width: 100%"> <input type="hidden" class="-spec-select -spec-select-level-2" style="width: 100%">
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-spec-select -spec-select-level-3" style="width: 100%"> <input type="hidden" class="-spec-select -spec-select-level-3" style="width: 100%">
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-spec-select -spec-select-level-4" style="width: 100%"> <input type="hidden" class="-spec-select -spec-select-level-4" style="width: 100%">
</div> </div>
<input type="hidden" class="-chosen-spec-id" name="{{ form.specialization.html_name }}" value="{{ form.specialization.value }}"> <input type="hidden" name="{{ form.specialization.html_name }}" value="{{ form.specialization.value }}" class="-chosen-spec-id">
</div> </div>
<div class="titleF1 titleF2 disTab"> <div class="titleF1 titleF2 disTab">
<div class="col-lg-12">Бюджет <span style="color: red">{{ form.budget.errors.as_text }}</span></div> <div class="col-lg-12">Бюджет <span style="color: red">{{ form.budget.errors.as_text }}</span></div>
@ -258,19 +260,19 @@
</div> </div>
</div> </div>
<div class="titleF1 disTab"> <div class="titleF1 disTab">
<div class="col-lg-12">Местоположение:</div> <div class="col-lg-12">Местоположение <span style="color: red">{{ realty_form.location.errors.as_text }}</span></div>
</div> </div>
<div class="polsF1 polsF2 disTab"> <div class="polsF1 polsF2 disTab">
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-location-select -location-select-country" style="width: 100%"> <input type="hidden" class="-location-select -location-select-country" style="width: 100%">
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-location-select -location-select-region" style="width: 100%"> <input type="hidden" class="-location-select -location-select-region" style="width: 100%">
</div> </div>
<div class="col-lg-3"> <div class="col-lg-3">
<input type='hidden' class="-location-select -location-select-city" style="width: 100%"> <input type="hidden" class="-location-select -location-select-city" style="width: 100%">
</div> </div>
<input type="hidden" id="chosenLocationId" name="{{ realty_form.location.html_name }}" value="{{ realty_form.location.value }}"> <input type="hidden" id="chosenLocationId" name="{{ realty_form.location.html_name }}" value="{{ realty_form.location.value }}">
@ -288,17 +290,11 @@
<p>Требуется допуск СРО</p> <p>Требуется допуск СРО</p>
</div> </div>
</div> </div>
<div class="searchF1 polsF1 polsFF links-filter">
<input class="btn-submit-link" type="submit" value="Сохранить">
</div>
</div> </div>
<div class="searchF1 polsF1 polsFF links-filter">
<input class="btn-submit-link" type="submit" value="Сохранить">
</div>
</div> </div>
</div> </div>
</form> </form>

@ -0,0 +1,151 @@
{% extends 'partials/base.html' %}
{% load staticfiles %}
{% load thumbnail %}
{% block content %}
{% include 'partials/header.html' %}
<div class="container mainScore">
<div class="row">
<div class="col-lg-12 allProjects">
<p class="titleScore">{{ object }}</p>
{% if object.get_prev %}
<div class="all all1">
<p><a href="{% url 'projects:contractor-portfolio-detail' object.get_prev.pk %}"> предыдущий</a></p>
</div>
{% endif %}
{% if object.get_next %}
<div class="all all2">
<p><a href="{% url 'projects:contractor-portfolio-detail' object.get_next.pk %}">следующий</a></p>
</div>
{% endif %}
</div>
{# <div class="btnReadyBlock disTab">#}
{# <div class="triangle1"></div>#}
{# <div class="col-lg-3">#}
{# <p class="cenaReady">{{ object.budget }} <i class="fa fa-rub"></i></p>#}
{# </div>#}
{# {% if request.user.is_authenticated %}#}
{# <div class="col-lg-3">#}
{# <a href="{% url 'users:contractor-profile' object.contractor.pk %}" class="linkReady LR1">#}
{# купить#}
{# </a>#}
{# </div>#}
{##}
{# <div class="col-lg-3">#}
{# <a href="{% url 'chat:chat-user' %}?user_id={{ object.contractor.pk }}" class="linkReady LR2">#}
{# Написать сообщение#}
{# </a>#}
{# </div>#}
{# {% endif %}#}
{# <div class="col-lg-3">#}
{# <a href="{% url 'users:contractor-profile' object.contractor.pk %}" class="linkReady LR3">#}
{# посмотреть контакты#}
{# </a>#}
{# </div>#}
{# </div>#}
<div class="desReadyBlock">
{% if object.text %}
<div class="col-lg-10 col-lg-offset-1">
<p class="titleReady">Описание:</p>
</div>
<div class="col-lg-10 col-lg-offset-1">
<p class="textReady">
{{ object.text }}
</p>
</div>
{% endif %}
<div class="col-lg-12 sliderReady">
<div class="arroww prev3"></div>
<div class="arroww next3"></div>
<div class="swiper-container gallery-top">
<div class="swiper-wrapper">
<div class="swiper-slide" style="background-image:url('{% static object.get_cover.url %}')"></div>
{% for photo in object.photos.all %}
<div class="swiper-slide" style="background-image:url('{% static photo.img.url %}')"></div>
{% endfor %}
</div>
</div>
<div class="swiper-container gallery-thumbs">
<div class="swiper-wrapper">
<div class="swiper-slide is-selected"
style="background-image:url('{% static object.get_cover.url %}')"></div>
{% for photo in object.photos.all %}
<div class="swiper-slide" style="background-image:url('{% static photo.img.url %}')"></div>
{% endfor %}
</div>
</div>
</div>
{# <div class="col-lg-12">#}
{# <div class="triangle2"></div>#}
{# <div class="arr arr1"></div>#}
{# <div class="arr arr2"></div>#}
{# </div>#}
</div>
{# <div class="col-lg-12 sibl">#}
{# <p>Похожие работы</p>#}
{# </div>#}
{# <div class="gallMini disTab">#}
{# {% for worksell in worksell_related %}#}
{# <div class="col-lg-3">#}
{##}
{# <div class="insetCol box-sizing disTab">#}
{# {% thumbnail worksell.get_cover "265x265" crop="center" as im %}#}
{# <div class="imgGal" style="background: url('{{ im.url }}') no-repeat center;">#}
{# <div class="imgFigure"></div>#}
{# </div>#}
{# {% endthumbnail %}#}
{# </div>#}
{# </div>#}
{# {% endfor %}#}
{# </div>#}
{% include 'partials/footer.html' %}
</div>
</div>
{% endblock %}
{% block js_block %}
<script src="{% static 'js/swiper.min.js' %}"></script>
<script>
var galleryTop = new Swiper('.gallery-top', {
nextButton: '.next3',
prevButton: '.prev3',
spaceBetween: 10,
});
var galleryTop = new Swiper('.gallery-top', {
nextButton: '.next3',
prevButton: '.prev3',
spaceBetween: 15,
onSlideChangeEnd: function (swiper) {
var activeIndex = swiper.activeIndex;
$(galleryThumbs.slides).removeClass('is-selected');
$(galleryThumbs.slides).eq(activeIndex).addClass('is-selected');
galleryThumbs.slideTo(activeIndex, 300, false);
}
});
var galleryThumbs = new Swiper('.gallery-thumbs', {
spaceBetween: 15,
freeMode: true,
centeredSlides: false,
slidesPerView: 6,
touchRatio: 0.2,
nextButton: '.arr2',
prevButton: '.arr1',
onClick: function (swiper, event) {
var clicked = swiper.clickedIndex
swiper.activeIndex = clicked;
swiper.updateClasses()
$(swiper.slides).removeClass('is-selected');
$(swiper.clickedSlide).addClass('is-selected');
galleryTop.slideTo(clicked, 300, false);
}
});
</script>
{% endblock %}

@ -3,27 +3,28 @@
{% load humanize %} {% load humanize %}
{% load thumbnail %} {% load thumbnail %}
{% load specializtions_tags %} {% load specializtions_tags %}
{% load project_tags %}
{% block content %} {% block content %}
{% include 'partials/header.html' %} {% include 'partials/header.html' %}
<div class="container mainScore"> <div class="container mainScore">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<p class="titleScore">{{ project.name }}</p> <p class="titleScore">{{ project.name }}</p>
</div> </div>
<div class="doneBlock new-done disTab"> <div class="doneBlock new-done disTab">
<div class="triangle1"></div> <div class="triangle1"></div>
{% if request.user.is_contractor %} {% if request.user.is_contractor %}
<div class="col-lg-12 new-p"> <div class="col-lg-12 new-p">
<p>{{ project.budget|intcomma }} <i class="fa fa-rub"></i></p> <p>{{ project.budget|intcomma }} <i class="fa fa-rub"></i></p>
</div> </div>
{% endif %} {% endif %}
<div class="col-lg-4"> <div class="col-lg-4">
<a href="#" class="aLinkExe"> <a href="#" class="aLinkExe">
<div class="imgExecutor"> <div class="imgExecutor">
{% if project.customer.avatar %} {% if project.customer.avatar %}
<img src="{{ project.customer.avatar.url }}" alt="execitor-image"> <img src="{{ project.customer.avatar.url }}" alt="execitor-image">
@ -31,13 +32,26 @@
<img src="{% static 'img/profile.jpg' %}" alt="execitor-image"> <img src="{% static 'img/profile.jpg' %}" alt="execitor-image">
{% endif %} {% endif %}
</div> </div>
</a> </a>
<p class="nameExecutor"> <p class="nameExecutor">
<a href="{% url 'users:customer-profile-open-projects' project.customer.pk %}">{{ project.customer.get_full_name }} [{{ project.customer.username }}]</a> <a href="{% url 'users:customer-profile-open-projects' project.customer.pk %}">{{ project.customer.get_full_name }} [{{ project.customer.username }}]</a>
</p> </p>
<p class="navv2">На сайте {{ project.created|naturaltime }}</p> <p class="navv2">На сайте {{ project.created|naturaltime }}</p>
</div>
{% if not request.user.is_contractor %}
<ul class="rettList restList3">
<li>
<a href="javascript:void(0)">Отзывы:
<span> + 0</span>
<small> 0</small>
<mark> - 0</mark>
</a>
</li>
</ul>
{% endif %}
</div>
{% if request.user.is_contractor %}
<div class="col-lg-3 retts"> <div class="col-lg-3 retts">
<ul class="rettList restList2"> <ul class="rettList restList2">
<li>Рейтинг: <span> 0</span></li> <li>Рейтинг: <span> 0</span></li>
@ -51,43 +65,48 @@
</li> </li>
</ul> </ul>
</div> </div>
{% endif %}
<div class="col-lg-3 retts new-list"> <div class="col-lg-3 retts new-list">
<ul class="desListPro"> <ul class="desListPro">
<li>{{ project.created }}</li> <li>
<li>{{ project.get_work_type_display }}</li> {{ project.created }}
</ul> </li>
<li>
{{ project.get_work_type_display }}
</li>
</ul>
{% if project.cro %} {% if project.cro %}
<div class="sroUser sroExecutor sroPro"> <div class="sroUser sroExecutor sroPro">
<div class="iconSRO"></div> <div class="iconSRO"></div>
<p>Есть допуск СРО</p> <p>Есть допуск СРО</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="infoProjectBlock info-new disTab"> <div class="infoProjectBlock info-new disTab">
<div class="triangle2"></div> <div class="triangle2"></div>
{% if project.realty %} {% if project.realty %}
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
<ul class="listProjectIn"> <ul class="listProjectIn">
<li><span>Местоположение:</span> {{ project.realty.location.name }}</li> <li><span>Местоположение:</span> {{ project.realty.location.name }}</li>
<li><span>Классификация здания:</span> {{ project.realty.building_classification.name }}</li> <li><span>Классификация здания:</span> {{ project.realty.building_classification.name }}</li>
<li><span>Вид строительства:</span> {{ project.realty.type_construction.name }}</li> <li><span>Вид строительства:</span> {{ project.realty.construction_type.name }}</li>
</ul> </ul>
</div> </div>
{% endif %} {% endif %}
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
<p class="textProIn"> <p class="textProIn">
{{ project.text }} {{ project.text }}
</p> </p>
</div> </div>
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
{% if request.user.is_contractor and not answer %} {% if request.user.is_contractor and not answer %}
<a href="#" onclick="$('.-project-answer-form').toggle('slow'); return false" class="new-answer"> <a href="#" onclick="$('.-project-answer-form').toggle('slow'); return false" class="new-answer">
Ответить на проект Ответить на проект
</a> </a>
@ -109,7 +128,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% elif request.user.is_customer %} {% elif request.user.is_customer and request.user == project.customer %}
<a href="{% url 'projects:customer-project-edit' pk=project.pk %}?back={{ request.path }}" class="linkProIn linkProIn1"> <a href="{% url 'projects:customer-project-edit' pk=project.pk %}?back={{ request.path }}" class="linkProIn linkProIn1">
Редактировать Редактировать
</a> </a>
@ -123,8 +142,28 @@
</a> </a>
</form> </form>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -137,12 +176,6 @@
{% if request.user.is_contractor %} {% if request.user.is_contractor %}
{% if answer %} {% if answer %}
<div class="candidateBlock disTab"> <div class="candidateBlock disTab">
<div class="candidate can-new"> <div class="candidate can-new">
@ -212,7 +245,7 @@
<p>Опубликован: {{ answer.created|date:'M d, Y' }}</p> <p>Опубликован: {{ answer.created|date:'M d, Y' }}</p>
</div> </div>
<div class="col-lg-3 retts"> <div class="col-lg-3 retts">
<form action="{% url 'projects:contractor-reject-project-answer' pk=answer.pk %}" method="POST" novalidate> <form action="{% url 'projects:reject-project-answer' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}"> <input type="hidden" name="next" value="{{ request.path }}">
<a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink3">отказ</a> <a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink3">отказ</a>
@ -262,7 +295,7 @@
</span> </span>
<p class="textComm44"> <p class="textComm44">
{{ message.text }} {{ message.text|linebreaksbr }}
</p> </p>
</div> </div>
</div> </div>
@ -421,34 +454,49 @@
{% elif request.user.is_customer and project in request.user.projects.all %}
{% elif request.user.is_customer and project in request.user.projects.all %}
<div class="exBigBlock disTab"> <div class="exBigBlock disTab">
<div class="col-lg-12"> <div class="col-lg-12">
<p class="titleEx">Исполнители</p> <p class="titleEx">Исполнители</p>
</div> </div>
<div class="col-lg-12 exButton"> <div class="col-lg-12 exButton">
<div class="btn-group" role="group" aria-label="..."> <div class="btn-group" role="group">
<button type="button" class="btn btn-default"> <a href="#new-answers" data-toggle="tab" class="btn btn-default">
Новые <span>{{ project.answers.count|sub:project.candidates.count }}</span> Новые <span>{{ project|get_new_answers|length }}</span>
</button> </a>
<button type="button" class="btn btn-default"> <a href="#candidate-answers" data-toggle="tab" class="btn btn-default">
Кандидаты <span>{{ project.candidates.count }}</span> Кандидаты <span>{{ project|get_candidate_answers|length }}</span>
</button> </a>
<button type="button" class="btn btn-default"> <a href="#rejected-answers" data-toggle="tab" class="btn btn-default">
Отказал <span>{{ project.rejected_answers_count }}</span> Отказы <span>{{ project|get_rejected_answers|length }}</span>
</button> </a>
</div> </div>
</div> </div>
</div> </div>
{% if project.answers.exists %} {% if project.answers.exists %}
{% if project.candidates.count > 1 %} {% if project.candidates.count > 1 %}
<div class="col-lg-12 compareBlock"> <div class="col-lg-12 compareBlock">
@ -458,168 +506,438 @@
</div> </div>
{% endif %} {% endif %}
<div class="col-lg-12 exNew"> <div class="tab-content">
<p>Новые исполнители</p> <div id="new-answers" class="tab-pane fade in active">
</div> <div class="col-lg-12 exNew">
<p>Новые исполнители</p>
{% for answer in project.answers.all %} </div>
<div class="candidateBlock disTab">
<div class="candidate"> {% for answer in project|get_new_answers %}
<div class="col-lg-4"> <div class="candidateBlock disTab">
<a href="#" class="aLinkExe"> <div class="candidate">
<div class="imgExecutor"> <div class="col-lg-4">
{% if answer.author.avatar %} <a href="#" class="aLinkExe">
<img src="{{ answer.author.avatar.url }}" alt="execitor-image"> <div class="imgExecutor">
{% else %} {% if answer.author.avatar %}
<img src="{% static 'img/profile.jpg' %}" alt="execitor-image"> <img src="{{ answer.author.avatar.url }}" alt="execitor-image">
{% else %}
<img src="{% static 'img/profile.jpg' %}" alt="execitor-image">
{% endif %}
</div>
</a>
<p class="nameExecutor">
{% if answer.author|class_name == 'User' %}
<a href="#">{{ answer.author.get_full_name }} [{{ answer.author.username }}]</a>
{% elif answer.author|class_name == 'Team' %}
<a href="#">{{ answer.author.name }}</a>
{% endif %}
</p>
<p class="navv2">На сайте {{ answer.author.created }}</p>
{% if answer.author|class_name == 'User' %}
{% if answer.author.contractor_status == 'free' %}
<div class="statusUser">Свободен</div><!-- ............. -->
{% endif %}
{% endif %} {% endif %}
</div> </div>
</a>
<div class="col-lg-3 retts">
<p class="nameExecutor"> {% ratings_widget answer.author.pk 'restList2' %}
{% if answer.author|class_name == 'User' %}
<a href="#">{{ answer.author.get_full_name }} [{{ answer.author.username }}]</a> {% if answer.author|class_name == 'User' and answer.author.cro %}
{% elif answer.author|class_name == 'Team' %} <div class="sroUser sroExecutor">
<a href="#">{{ answer.author.name }}</a> <div class="iconSRO"></div>
{% endif %} <p>Есть допуск СРО</p>
</p> </div>
{% elif answer.author|class_name == 'Team' %}
<p class="navv2">На сайте {{ answer.author.created }}</p> <!-- TODO -->
{% if answer.author|class_name == 'User' %} <div class="sroUser sroExecutor">
{% if answer.author.contractor_status == 'free' %} <div class="iconSRO"></div>
<div class="statusUser">Свободен</div><!-- ............. --> <p>Есть допуск СРО</p>
{% endif %} </div>
{% endif %} {% endif %}
</div>
<div class="col-lg-3 retts">
{% ratings_widget answer.author.pk 'restList2' %}
{% if answer.author|class_name == 'User' and answer.author.cro %}
<div class="sroUser sroExecutor">
<div class="iconSRO"></div>
<p>Есть допуск СРО</p>
</div> </div>
{% elif answer.author|class_name == 'Team' %}
<!-- TODO -->
<div class="sroUser sroExecutor"> <div class="col-lg-2 listCens">
<div class="iconSRO"></div> <p>
<p>Есть допуск СРО</p> Цена:
<span>{{ answer.budget|intcomma }}</span>
<i class="fa fa-rub"></i>
</p>
<p>
Срок: <span>{{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }}</span>
</p>
<p>Опубликован: {{ answer.created|date:'M d, Y' }}</p>
</div> </div>
{% endif %}
</div> <div class="col-lg-3 retts">
{% if answer.author not in project|get_candidates %}
<div class="col-lg-2 listCens"> <a href="{% url 'projects:add-candidate' answer_id=answer.pk project_id=project.pk %}" class="candLink candLink1">
<p> Кандидат
Цена: </a>
<span>{{ answer.budget|intcomma }}</span> {% endif %}
<i class="fa fa-rub"></i>
</p> {% if not project.order.contractor and not project.order.team %}
<p> <form action="{% url 'projects:customer-offer-order' answer_id=answer.pk project_id=project.pk %}" method="POST" novalidate>
Срок: <span>{{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }}</span> {% csrf_token %}
</p>
<p>Опубликован: {{ answer.created|date:'M d, Y' }}</p> <a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink2">
</div> предложить проект
</a>
<div class="col-lg-3 retts"> </form>
<a href="{% url 'projects:add-candidate' answer_id=answer.pk project_id=project.pk %}" class="candLink candLink1"> {% endif %}
Кандидат
</a> <form action="{% url 'projects:reject-project-answer' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<form action="{% url 'projects:customer-offer-order' answer_id=answer.pk project_id=project.pk %}" method="POST" novalidate> <a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink3">отказ</a>
{% csrf_token %} </form>
<a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink2"> </div>
предложить проект
</a> <div class="gallMini disTab">
</form> {% for portf in answer.portfolios.all %}
<div class="col-lg-3">
<form action="{% url 'projects:customer-reject-project-answer' pk=answer.pk %}" method="POST" novalidate> <div class="insetCol box-sizing disTab">
{% csrf_token %} <div class="imgGal" style="background: url('{{ portf.photos.first.img.url }}') no-repeat center">
<input type="hidden" name="next" value="{{ request.path }}"> <div class="imgFigure"></div>
<a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink3">отказ</a> </div>
</form> </div>
</div> </div>
{% endfor %}
<div class="gallMini disTab"> </div>
{% for portf in answer.portfolios.all %}
<div class="col-lg-3"> <div class="commBlock44 disTab">
<div class="insetCol box-sizing disTab"> {% for message in answer.messages.all %}
<div class="imgGal" style="background: url('{{ portf.photos.first.img.url }}') no-repeat center"> <div class="comm44 disTab">
<div class="imgFigure"></div> <div class="col-lg-10 col-lg-offset-1">
{% if message.is_sender_customer %}
<p class="nameComm nameCommAct">
{{ project.customer.get_full_name }}
</p>
{% else %}
<p class="nameComm">
{% if answer.author|class_name == 'User' %}
{{ answer.author.get_full_name }}
{% elif answer.author|class_name == 'Team' %}
{{ answer.author.name }}
{% endif %}
</p>
{% endif %}
<span class="dateComm44">
{{ message.created }}
</span>
<p class="textComm44">
{{ message.text|linebreaksbr }}
</p>
</div>
</div>
{% endfor %}
<div class="comm44 disTab">
<!--
<div class="i col-lg-10 col-lg-offset-1">
<a href="#" onclick="return false" class="answerComm">
Ответить
</a>
</div>
-->
<div class="col-lg-10 col-lg-offset-1">
<form action="{% url 'projects:create-answer-message' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">
<div><textarea name="text"></textarea></div>
<div><button type="submit">Отправить</button></div>
</form>
</div> </div>
</div> </div>
</div> </div>
{% endfor %} </div>
</div> </div>
{% endfor %}
<div class="commBlock44 disTab"> </div>
{% for message in answer.messages.all %}
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
{% if message.is_sender_customer %}
<p class="nameComm nameCommAct">
{{ project.customer.get_full_name }} <div id="candidate-answers" class="tab-pane fade">
</p> <div class="col-lg-12 exNew">
{% else %} <p>Кандидаты</p>
<p class="nameComm"> </div>
{% if answer.author|class_name == 'User' %}
{{ answer.author.get_full_name }} {% for answer in project|get_candidate_answers %}
{% elif answer.author|class_name == 'Team' %} <div class="candidateBlock disTab">
{{ answer.author.name }} <div class="candidate">
{% endif %} <div class="col-lg-4">
</p> <a href="#" class="aLinkExe">
<div class="imgExecutor">
{% if answer.author.avatar %}
<img src="{{ answer.author.avatar.url }}" alt="execitor-image">
{% else %}
<img src="{% static 'img/profile.jpg' %}" alt="execitor-image">
{% endif %}
</div>
</a>
<p class="nameExecutor">
{% if answer.author|class_name == 'User' %}
<a href="#">{{ answer.author.get_full_name }} [{{ answer.author.username }}]</a>
{% elif answer.author|class_name == 'Team' %}
<a href="#">{{ answer.author.name }}</a>
{% endif %} {% endif %}
</p>
<p class="navv2">На сайте {{ answer.author.created }}</p>
{% if answer.author|class_name == 'User' %}
{% if answer.author.contractor_status == 'free' %}
<div class="statusUser">Свободен</div><!-- ............. -->
{% endif %}
{% endif %}
</div>
<div class="col-lg-3 retts">
{% ratings_widget answer.author.pk 'restList2' %}
{% if answer.author|class_name == 'User' and answer.author.cro %}
<div class="sroUser sroExecutor">
<div class="iconSRO"></div>
<p>Есть допуск СРО</p>
</div>
{% elif answer.author|class_name == 'Team' %}
<!-- TODO -->
<span class="dateComm44"> <div class="sroUser sroExecutor">
{{ message.created }} <div class="iconSRO"></div>
</span> <p>Есть допуск СРО</p>
</div>
<p class="textComm44"> {% endif %}
{{ message.text }}
</p>
</div>
</div> </div>
{% endfor %}
<div class="comm44 disTab">
{# <div class="i col-lg-10 col-lg-offset-1">#}
{# <a href="#" onclick="return false" class="answerComm">#}
{# Ответить#}
{# </a>#}
{# </div>#}
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-2 listCens">
<form action="{% url 'projects:create-answer-message' pk=answer.pk %}" method="POST" novalidate> <p>
Цена:
<span>{{ answer.budget|intcomma }}</span>
<i class="fa fa-rub"></i>
</p>
<p>
Срок: <span>{{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }}</span>
</p>
<p>Опубликован: {{ answer.created|date:'M d, Y' }}</p>
</div>
<div class="col-lg-3 retts">
{% if not project.order.contractor and not project.order.team %}
<form action="{% url 'projects:customer-offer-order' answer_id=answer.pk project_id=project.pk %}" method="POST" novalidate>
{% csrf_token %}
<a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink2">
предложить проект
</a>
</form>
{% endif %}
<form action="{% url 'projects:reject-project-answer' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}"> <input type="hidden" name="next" value="{{ request.path }}">
<a href="#" onclick="$(this).closest('form').submit(); return false" class="candLink candLink3">отказ</a>
<div><textarea name="text"></textarea></div>
<div><button type="submit">Отправить</button></div>
</form> </form>
</div> </div>
<div class="gallMini disTab">
{% for portf in answer.portfolios.all %}
<div class="col-lg-3">
<div class="insetCol box-sizing disTab">
<div class="imgGal" style="background: url('{{ portf.photos.first.img.url }}') no-repeat center">
<div class="imgFigure"></div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="commBlock44 disTab">
{% for message in answer.messages.all %}
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
{% if message.is_sender_customer %}
<p class="nameComm nameCommAct">
{{ project.customer.get_full_name }}
</p>
{% else %}
<p class="nameComm">
{% if answer.author|class_name == 'User' %}
{{ answer.author.get_full_name }}
{% elif answer.author|class_name == 'Team' %}
{{ answer.author.name }}
{% endif %}
</p>
{% endif %}
<span class="dateComm44">
{{ message.created }}
</span>
<p class="textComm44">
{{ message.text|linebreaksbr }}
</p>
</div>
</div>
{% endfor %}
<div class="comm44 disTab">
<!--
<div class="i col-lg-10 col-lg-offset-1">
<a href="#" onclick="return false" class="answerComm">
Ответить
</a>
</div>
-->
<div class="col-lg-10 col-lg-offset-1">
<form action="{% url 'projects:create-answer-message' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">
<div><textarea name="text"></textarea></div>
<div><button type="submit">Отправить</button></div>
</form>
</div>
</div>
</div>
</div> </div>
</div> </div>
{% endfor %}
</div>
<div id="rejected-answers" class="tab-pane fade">
<div class="col-lg-12 exNew">
<p>Отказы</p>
</div> </div>
{% for answer in project|get_rejected_answers %}
<div class="candidateBlock disTab">
<div class="candidate">
<div class="col-lg-4">
<a href="#" class="aLinkExe">
<div class="imgExecutor">
{% if answer.author.avatar %}
<img src="{{ answer.author.avatar.url }}" alt="execitor-image">
{% else %}
<img src="{% static 'img/profile.jpg' %}" alt="execitor-image">
{% endif %}
</div>
</a>
<p class="nameExecutor">
{% if answer.author|class_name == 'User' %}
<a href="#">{{ answer.author.get_full_name }} [{{ answer.author.username }}]</a>
{% elif answer.author|class_name == 'Team' %}
<a href="#">{{ answer.author.name }}</a>
{% endif %}
</p>
<p class="navv2">На сайте {{ answer.author.created }}</p>
{% if answer.author|class_name == 'User' %}
{% if answer.author.contractor_status == 'free' %}
<div class="statusUser">Свободен</div><!-- ............. -->
{% endif %}
{% endif %}
</div>
<div class="col-lg-3 retts">
{% ratings_widget answer.author.pk 'restList2' %}
{% if answer.author|class_name == 'User' and answer.author.cro %}
<div class="sroUser sroExecutor">
<div class="iconSRO"></div>
<p>Есть допуск СРО</p>
</div>
{% elif answer.author|class_name == 'Team' %}
<!-- TODO -->
<div class="sroUser sroExecutor">
<div class="iconSRO"></div>
<p>Есть допуск СРО</p>
</div>
{% endif %}
</div>
<div class="col-lg-2 listCens">
<p>
Цена:
<span>{{ answer.budget|intcomma }}</span>
<i class="fa fa-rub"></i>
</p>
<p>
Срок: <span>{{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }}</span>
</p>
<p>Опубликован: {{ answer.created|date:'M d, Y' }}</p>
</div>
<div class="gallMini disTab">
{% for portf in answer.portfolios.all %}
<div class="col-lg-3">
<div class="insetCol box-sizing disTab">
<div class="imgGal" style="background: url('{{ portf.photos.first.img.url }}') no-repeat center">
<div class="imgFigure"></div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="commBlock44 disTab">
{% for message in answer.messages.all %}
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
{% if message.is_sender_customer %}
<p class="nameComm nameCommAct">
{{ project.customer.get_full_name }}
</p>
{% else %}
<p class="nameComm">
{% if answer.author|class_name == 'User' %}
{{ answer.author.get_full_name }}
{% elif answer.author|class_name == 'Team' %}
{{ answer.author.name }}
{% endif %}
</p>
{% endif %}
<span class="dateComm44">
{{ message.created }}
</span>
<p class="textComm44">
{{ message.text|linebreaksbr }}
</p>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div> </div>
{% endfor %} </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% include 'partials/footer.html' %} {% include 'partials/footer.html' %}
</div> </div>
</div> </div>

@ -5,11 +5,11 @@
{% block content %} {% block content %}
{% include 'partials/header.html' %} {% include 'partials/header.html' %}
<div class="container mainScore"> <div class="container mainScore">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<p class="titleScore">Биржа проектов</p> <p class="titleScore">Биржа проектов</p>
</div> </div>
<form action="{% url 'projects:project-filter' %}" method="GET" novalidate> <form action="{% url 'projects:project-filter' %}" method="GET" novalidate>
<div class="col-lg-12"> <div class="col-lg-12">
@ -50,7 +50,7 @@
</div> </div>
<div class="searchF1"> <div class="searchF1 resSearchF1">
<div class="col-lg-6"> <div class="col-lg-6">
<input <input
type="text" type="text"
@ -167,7 +167,7 @@
<input type="checkbox" name="{{ form.reverse_order.html_name }}" {% if reverse_order %}checked{% endif %} style="display: none"> <input type="checkbox" name="{{ form.reverse_order.html_name }}" {% if reverse_order %}checked{% endif %} style="display: none">
</div> </div>
</div> </div>
</form> </form>
@ -230,11 +230,11 @@
<div class="col-lg-12 pagin"> <div class="col-lg-12 pagin">
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
</div> </div>
{% include 'partials/footer.html' %} {% include 'partials/footer.html' %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

@ -0,0 +1,26 @@
from django import template
from pprint import pprint, pformat
from archilance import util
register = template.Library()
@register.filter
def get_candidates(project):
return tuple(c.answer.author for c in project.candidates.all())
@register.filter
def get_new_answers(project):
return set(project.answers.filter(rejected=False)) - set(c.answer for c in project.candidates.filter(answer__rejected=False))
@register.filter
def get_candidate_answers(project):
return tuple(c.answer for c in project.candidates.filter(answer__rejected=False))
@register.filter
def get_rejected_answers(project):
return project.answers.filter(rejected=True)
# import code; code.interact(local=dict(globals(), **locals()))

@ -3,24 +3,24 @@ from django.views.generic import TemplateView
from .views import ( from .views import (
add_candidate, add_candidate,
CandidateDeleteView,
contractor_portfolio_create, contractor_portfolio_create,
ContractorAnswerArchiveView, ContractorAnswerArchiveView,
ContractorPortfolioTrashView, ContractorPortfolioTrashView,
ContractorPortfolioUpdateView, ContractorPortfolioUpdateView,
ContractorRejectProjectAnswerView,
CustomerProjectCreateView, CustomerProjectCreateView,
CustomerProjectDeleteView, CustomerProjectDeleteView,
CustomerProjectEditView, CustomerProjectEditView,
CustomerProjectRestoreView, CustomerProjectRestoreView,
CustomerProjectTrashView, CustomerProjectTrashView,
CustomerRejectProjectAnswerView,
OfferOrderView, OfferOrderView,
ProjectAnswerCreateMessageView, ProjectAnswerCreateMessageView,
ProjectComparisonView, ProjectComparisonView,
ProjectDetailWithAnswerView, ProjectDetailWithAnswerView,
ProjectFilterView, ProjectFilterView,
RejectProjectAnswerView,
sort_candidates, sort_candidates,
CandidateDeleteView, PortfolioDetail,
) )
app_name = 'projects' app_name = 'projects'
@ -38,12 +38,12 @@ urlpatterns = [
urls.url(r'^(?P<pk>\d+)/delete/$', CustomerProjectDeleteView.as_view(), name='customer-project-delete'), urls.url(r'^(?P<pk>\d+)/delete/$', CustomerProjectDeleteView.as_view(), name='customer-project-delete'),
urls.url(r'^create-answer-message/(?P<pk>\d+)/$', ProjectAnswerCreateMessageView.as_view(), name='create-answer-message'), urls.url(r'^create-answer-message/(?P<pk>\d+)/$', ProjectAnswerCreateMessageView.as_view(), name='create-answer-message'),
urls.url(r'^contractor-reject-project-answer/(?P<pk>\d+)/$', ContractorRejectProjectAnswerView.as_view(), name='contractor-reject-project-answer'), urls.url(r'^reject-project-answer/(?P<pk>\d+)/$', RejectProjectAnswerView.as_view(), name='reject-project-answer'),
urls.url(r'^customer-reject-project-answer/(?P<pk>\d+)/$', CustomerRejectProjectAnswerView.as_view(), name='customer-reject-project-answer'),
urls.url(r'^portfolio/create/$', contractor_portfolio_create, name='contractor-portfolio-create'), urls.url(r'^portfolio/create/$', contractor_portfolio_create, name='contractor-portfolio-create'),
urls.url(r'^portfolio/(?P<pk>\d+)/?$', PortfolioDetail.as_view(), name='contractor-portfolio-detail'),
urls.url(r'^portfolio/(?P<pk>\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'), urls.url(r'^portfolio/(?P<pk>\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'),
urls.url(r'^candidate/add/(?P<answer_id>(\d+))/(?P<project_id>(\d+))/$', add_candidate, name='add-candidate'), urls.url(r'^candidate/add/(?P<answer_id>(\d+))/(?P<project_id>(\d+))/$', add_candidate, name='add-candidate'),
urls.url(r'^candidate/delete/(?P<pk>(\d+))/$', CandidateDeleteView.as_view(), name='delete-candidate'), urls.url(r'^candidate/delete/(?P<pk>(\d+))/$', CandidateDeleteView.as_view(), name='delete-candidate'),
urls.url(r'^candidate/comparison/sort/$', sort_candidates, name='comparison-sort'), urls.url(r'^candidate/comparison/sort/$', sort_candidates, name='comparison-sort'),

@ -2,6 +2,7 @@ from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
@ -42,78 +43,78 @@ from .forms import (
class ProjectDetailWithAnswerView(BaseMixin, View): class ProjectDetailWithAnswerView(BaseMixin, View):
form_class = ProjectAnswerForm form_class = ProjectAnswerForm
template_name = 'project_detail.html' template_name = 'project_detail.html'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
context = self.get_context_data(**_.merge({}, request.GET, kwargs)) context = self.get_context_data(**_.merge({}, request.GET, kwargs))
project = get_object_or_404(Project, pk=kwargs.get('pk')) project = get_object_or_404(Project, pk=kwargs.get('pk'))
context.update({'project': project}) context.update({'project': project})
hit_count = HitCount.objects.get_for_object(project) hit_count = HitCount.objects.get_for_object(project)
HitCountMixin.hit_count(request, hit_count) HitCountMixin.hit_count(request, hit_count)
if request.user.is_authenticated() and request.user.is_contractor(): if request.user.is_authenticated() and request.user.is_contractor():
project_answers = project.answers.all() project_answers = project.answers.filter(rejected=False)
contractor = request.user contractor = request.user
answer = _.first(_.filter(project_answers, lambda a: a.author == contractor)) answer = _.first(_.filter(project_answers, lambda a: a.author == contractor))
if not answer: if not answer:
try: team = contractor.team try: team = contractor.team
except Team.DoesNotExist: team = None except Team.DoesNotExist: team = None
if team: if team:
answer = _.first(_.filter(project_answers, lambda a: a.author == team)) answer = _.first(_.filter(project_answers, lambda a: a.author == team))
context.update({'answer': answer}) context.update({'answer': answer})
if not answer: if not answer:
try: contractor.team; context.update({'can_answer_as_team': True}) try: contractor.team; context.update({'can_answer_as_team': True})
except Team.DoesNotExist: pass except Team.DoesNotExist: pass
if request.GET.get('answer_as_team') == 'on': if request.GET.get('answer_as_team') == 'on':
context.update({'answer_as_team': True}) context.update({'answer_as_team': True})
form = self.form_class(request=request, answer_as_team=True, project=project) form = self.form_class(request=request, answer_as_team=True, project=project)
else: else:
form = self.form_class(request=request, project=project) form = self.form_class(request=request, project=project)
context.update({'form': form}) context.update({'form': form})
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_contractor(): if request.user.is_authenticated() and request.user.is_contractor():
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
answer_as_team = None answer_as_team = None
project = get_object_or_404(Project, pk=kwargs.get('pk')) # TODO: Does this work? project = get_object_or_404(Project, pk=kwargs.get('pk')) # TODO: Does this work?
if request.POST.get('answer_as_team') == 'on': if request.POST.get('answer_as_team') == 'on':
answer_as_team = True answer_as_team = True
if answer_as_team: if answer_as_team:
form = self.form_class(request.POST, request=request, answer_as_team=True, project=project) form = self.form_class(request.POST, request=request, answer_as_team=True, project=project)
else: else:
form = self.form_class(request.POST, request=request, project=project) form = self.form_class(request.POST, request=request, project=project)
project = get_object_or_404(Project, pk=kwargs.get('pk')) project = get_object_or_404(Project, pk=kwargs.get('pk'))
context.update({'project': project}) context.update({'project': project})
if form.is_valid(): if form.is_valid():
answer = form.save(commit=False) answer = form.save(commit=False)
answer.project = project answer.project = project
answer.author = request.user.team if answer_as_team else request.user answer.author = request.user.team if answer_as_team else request.user
answer.save() answer.save()
form.save_m2m() form.save_m2m()
for file in request.FILES.getlist('new_files'): for file in request.FILES.getlist('new_files'):
if len(file.name) <= 255: if len(file.name) <= 255:
AnswerFile.objects.create(file=file, name=file.name, answer=answer) AnswerFile.objects.create(file=file, name=file.name, answer=answer)
message = AnswerMessage(text=form.cleaned_data.get('text'), is_sender_customer=False) message = AnswerMessage(text=form.cleaned_data.get('text'), is_sender_customer=False)
message.answer = answer message.answer = answer
message.contractor_or_team = answer.author message.contractor_or_team = answer.author
message.save() message.save()
messages.info(request, 'Отклик успешно размещён') messages.info(request, 'Отклик успешно размещён')
redirect_to = request.POST.get('next') redirect_to = request.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
@ -123,7 +124,7 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
'<p>Произошла ошибка (form)</p>' '<p>Произошла ошибка (form)</p>'
'<pre>{form}</pre>' '<pre>{form}</pre>'
).format(form=pformat(form.errors))) ).format(form=pformat(form.errors)))
context.update({'form': form}) context.update({'form': form})
return render(request, self.template_name, context) return render(request, self.template_name, context)
else: else:
@ -132,41 +133,41 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
class ProjectAnswerCreateMessageView(BaseMixin, View): class ProjectAnswerCreateMessageView(BaseMixin, View):
form_class = ProjectAnswerMessageForm form_class = ProjectAnswerMessageForm
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated(): if request.user.is_authenticated():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
return HttpResponseForbidden('403 Forbidden') return HttpResponseForbidden('403 Forbidden')
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request=request) form = self.form_class(request.POST, request=request)
if form.is_valid(): if form.is_valid():
message = form.save(commit=False) message = form.save(commit=False)
if request.user.is_contractor(): if request.user.is_contractor():
answer = get_object_or_404(Answer, pk=kwargs.get('pk')) answer = get_object_or_404(Answer, pk=kwargs.get('pk'))
message.answer = answer message.answer = answer
message.is_sender_customer = False message.is_sender_customer = False
if isinstance(answer.author, User) and answer.author == request.user: if isinstance(answer.author, User) and answer.author == request.user:
message.contractor_or_team = request.user message.contractor_or_team = request.user
elif isinstance(answer.author, Team): elif isinstance(answer.author, Team):
try: team = request.user.team try: team = request.user.team
except Team.DoesNotExist: team = None except Team.DoesNotExist: team = None
if team and answer.author == team: if team and answer.author == team:
message.contractor_or_team = team message.contractor_or_team = team
elif request.user.is_customer(): elif request.user.is_customer():
answer = get_object_or_404(Answer, pk=kwargs.get('pk')) # TODO: Perform additional checks answer = get_object_or_404(Answer, pk=kwargs.get('pk')) # TODO: Perform additional checks
message.answer = answer message.answer = answer
message.is_sender_customer = True message.is_sender_customer = True
message.save() message.save()
form.save_m2m() form.save_m2m()
messages.info(request, 'Сообщение успешно размещено') messages.info(request, 'Сообщение успешно размещено')
else: else:
if form.errors: if form.errors:
@ -174,49 +175,30 @@ class ProjectAnswerCreateMessageView(BaseMixin, View):
'<p>Произошла ошибка (form)</p>' '<p>Произошла ошибка (form)</p>'
'<pre>{form}</pre>' '<pre>{form}</pre>'
).format(form=pformat(form.errors))) ).format(form=pformat(form.errors)))
redirect_to = request.POST.get('next') redirect_to = request.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
class ContractorRejectProjectAnswerView(BaseMixin, View): class RejectProjectAnswerView(BaseMixin, View):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_contractor(): if request.user.is_authenticated():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
return HttpResponseForbidden('403 Forbidden') raise PermissionDenied
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
answer = get_object_or_404(request.user.contractor_answers, pk=kwargs.get('pk')) if request.user.is_contractor():
answer = get_object_or_404(request.user.contractor_answers, pk=kwargs.get('pk'))
answer.delete() elif request.user.is_customer():
project = get_object_or_404(request.user.projects, answers__pk=kwargs.get('pk'))
messages.info(request, 'Вы успешно отказались от проекта') answer = get_object_or_404(project.answers, pk=kwargs.get('pk'))
redirect_to = request.POST.get('next') answer.rejected = True
return redirect(redirect_to) answer.save()
messages.info(request, 'Успешный отказ от проекта')
class CustomerRejectProjectAnswerView(BaseMixin, View):
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_customer():
return super().dispatch(request, *args, **kwargs)
else:
return HttpResponseForbidden('403 Forbidden')
def post(self, request, *args, **kwargs):
answer_id = kwargs.get('pk')
project = get_object_or_404(request.user.projects, answers__pk=answer_id)
answer = get_object_or_404(project.answers, pk=answer_id)
answer.delete()
project.rejected_answers_count = F('rejected_answers_count') + 1
project.save()
messages.info(request, 'Вы успешно отказали пользователю в проекте')
redirect_to = request.POST.get('next') redirect_to = request.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
@ -225,107 +207,107 @@ class ProjectFilterView(BaseMixin, View):
template_name = 'project_filter.html' template_name = 'project_filter.html'
form_class = ProjectFilterForm form_class = ProjectFilterForm
realty_form = ProjectFilterRealtyForm realty_form = ProjectFilterRealtyForm
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
form = self.form_class(request.GET, request=request) form = self.form_class(request.GET, request=request)
realty_form = self.realty_form(request.GET, request=request, prefix='realty_form') realty_form = self.realty_form(request.GET, request=request, prefix='realty_form')
context = self.get_context_data(**_.merge({}, request.GET, kwargs)) context = self.get_context_data(**_.merge({}, request.GET, kwargs))
projects = Project.objects projects = Project.objects
if form.is_valid() and realty_form.is_valid(): if form.is_valid() and realty_form.is_valid():
ord = None ord = None
keywords = form.cleaned_data.get('keywords') keywords = form.cleaned_data.get('keywords')
cro = form.cleaned_data.get('cro') cro = form.cleaned_data.get('cro')
work_type = form.cleaned_data.get('work_type') work_type = form.cleaned_data.get('work_type')
specialization = form.cleaned_data.get('specialization') specialization = form.cleaned_data.get('specialization')
building_classification = realty_form.cleaned_data.get('building_classification') building_classification = realty_form.cleaned_data.get('building_classification')
construction_type = realty_form.cleaned_data.get('construction_type') construction_type = realty_form.cleaned_data.get('construction_type')
location = realty_form.cleaned_data.get('location') location = realty_form.cleaned_data.get('location')
if keywords: if keywords:
keywords = tuple(filter(None, re.split(r'\s|,|;', keywords))) keywords = tuple(filter(None, re.split(r'\s|,|;', keywords)))
for k in keywords: for k in keywords:
projects = projects.filter(Q(name__icontains=k) | Q(text__icontains=k)) projects = projects.filter(Q(name__icontains=k) | Q(text__icontains=k))
if cro: if cro:
projects = projects.filter(cro=cro) projects = projects.filter(cro=cro)
if work_type: if work_type:
projects = projects.filter(work_type=work_type) projects = projects.filter(work_type=work_type)
if specialization: if specialization:
projects = projects.filter( projects = projects.filter(
specialization__lft__gte=specialization.lft, specialization__lft__gte=specialization.lft,
specialization__rght__lte=specialization.rght, specialization__rght__lte=specialization.rght,
) )
if building_classification: if building_classification:
projects = projects.filter(realty__building_classification=building_classification) projects = projects.filter(realty__building_classification=building_classification)
if construction_type: if construction_type:
projects = projects.filter(realty__construction_type=construction_type) projects = projects.filter(realty__construction_type=construction_type)
if location: if location:
projects = projects.filter( projects = projects.filter(
realty__location__lft__gte=location.lft, realty__location__lft__gte=location.lft,
realty__location__rght__lte=location.rght, realty__location__rght__lte=location.rght,
) )
order_by = form.cleaned_data.get('order_by') order_by = form.cleaned_data.get('order_by')
last_order_by = form.cleaned_data.get('last_order_by') last_order_by = form.cleaned_data.get('last_order_by')
reverse_order = form.cleaned_data.get('reverse_order') reverse_order = form.cleaned_data.get('reverse_order')
if order_by: if order_by:
reverse_order = not reverse_order if order_by == last_order_by else False reverse_order = not reverse_order if order_by == last_order_by else False
ord = order_by ord = order_by
last_order_by = ord last_order_by = ord
elif last_order_by: elif last_order_by:
ord = last_order_by ord = last_order_by
manual_sort = None manual_sort = None
if ord and ord == 'views': if ord and ord == 'views':
projects = natsort.natsorted(projects.all(), key=lambda p: p.hit_count.hits, reverse=reverse_order) projects = natsort.natsorted(projects.all(), key=lambda p: p.hit_count.hits, reverse=reverse_order)
manual_sort = True manual_sort = True
elif ord: elif ord:
projects = projects.order_by('-%s' % ord if reverse_order else ord) projects = projects.order_by('-%s' % ord if reverse_order else ord)
context.update({ context.update({
'last_order_by': last_order_by, 'last_order_by': last_order_by,
'reverse_order': reverse_order, 'reverse_order': reverse_order,
}) })
project_count = len(projects) if manual_sort else projects.count() project_count = len(projects) if manual_sort else projects.count()
display_msg = 'Найдено %s проектов' % project_count if project_count > 0 else 'Ничего не найдено' display_msg = 'Найдено %s проектов' % project_count if project_count > 0 else 'Ничего не найдено'
else: else:
display_msg = 'Пожалуйста, введите корректные данные' display_msg = 'Пожалуйста, введите корректные данные'
if form.errors: if form.errors:
messages.info(request, ( messages.info(request, (
'<p>Произошла ошибка (form)</p>' '<p>Произошла ошибка (form)</p>'
'<pre>{form}</pre>' '<pre>{form}</pre>'
).format(form=pformat(form.errors))) ).format(form=pformat(form.errors)))
if realty_form and realty_form.errors: if realty_form and realty_form.errors:
messages.info(request, ( messages.info(request, (
'<p>Произошла ошибка (realty_form)</p>' '<p>Произошла ошибка (realty_form)</p>'
'<pre>{realty_form}</pre>' '<pre>{realty_form}</pre>'
).format(realty_form=pformat(realty_form.errors))) ).format(realty_form=pformat(realty_form.errors)))
paginator = Paginator(projects if manual_sort else projects.all(), settings.PAGE_SIZE) paginator = Paginator(projects if manual_sort else projects.all(), settings.PAGE_SIZE)
page = request.GET.get('page') page = request.GET.get('page')
try: try:
projects = paginator.page(page) projects = paginator.page(page)
except PageNotAnInteger: except PageNotAnInteger:
projects = paginator.page(1) projects = paginator.page(1)
except EmptyPage: except EmptyPage:
projects = paginator.page(paginator.num_pages) projects = paginator.page(paginator.num_pages)
context.update({ context.update({
'form': form, 'form': form,
'realty_form': realty_form, 'realty_form': realty_form,
@ -334,7 +316,7 @@ class ProjectFilterView(BaseMixin, View):
'page_obj': projects, 'page_obj': projects,
'display_msg': display_msg, 'display_msg': display_msg,
}) })
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -342,33 +324,33 @@ class CustomerProjectCreateView(BaseMixin, View):
form_class = CustomerProjectEditForm form_class = CustomerProjectEditForm
realty_form = RealtyForm realty_form = RealtyForm
template_name = 'customer_project_create.html' template_name = 'customer_project_create.html'
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_customer(): if request.user.is_authenticated() and request.user.is_customer():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
raise PermissionDenied raise PermissionDenied
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
form = self.form_class(request=request) form = self.form_class(request=request)
realty_form = self.realty_form(request=request, prefix='realty_form') realty_form = self.realty_form(request=request, prefix='realty_form')
context = self.get_context_data(**_.merge({}, request.GET, kwargs)) context = self.get_context_data(**_.merge({}, request.GET, kwargs))
context.update({'form': form, 'realty_form': realty_form}) context.update({'form': form, 'realty_form': realty_form})
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request=request) # Passing `request.FILES` seems unnecessary here. Files are added manually below form = self.form_class(request.POST, request=request) # Passing `request.FILES` seems unnecessary here. Files are added manually below
form.is_valid() form.is_valid()
realty = form.cleaned_data.get('realty') realty = form.cleaned_data.get('realty')
if realty: if realty:
realty_form = self.realty_form(request.POST, instance=realty, request=request, prefix='realty_form') realty_form = self.realty_form(request.POST, instance=realty, request=request, prefix='realty_form')
else: else:
realty_form = self.realty_form(request.POST, request=request, prefix='realty_form') realty_form = self.realty_form(request.POST, request=request, prefix='realty_form')
if form.is_valid() and realty_form.is_valid(): if form.is_valid() and realty_form.is_valid():
project = form.save(commit=False) project = form.save(commit=False)
project.customer = request.user project.customer = request.user
@ -378,12 +360,12 @@ class CustomerProjectCreateView(BaseMixin, View):
secure = False secure = False
if 'secure_deal' in project.deal_type: if 'secure_deal' in project.deal_type:
secure = True secure = True
Order.objects.create(project=project, secure=secure) Order.objects.create(project=project, secure=secure)
for file in request.FILES.getlist('new_files'): for file in request.FILES.getlist('new_files'):
ProjectFile.objects.create(file=file, project=project) ProjectFile.objects.create(file=file, project=project)
if realty: if realty:
realty_form.save() realty_form.save()
else: else:
@ -391,10 +373,10 @@ class CustomerProjectCreateView(BaseMixin, View):
realty.user = request.user realty.user = request.user
realty.save() realty.save()
realty_form.save_m2m() realty_form.save_m2m()
project.realty = realty # Connect a realty with a project project.realty = realty # Connect a realty with a project
project.save() project.save()
messages.info(request, 'Проект успешно создан') messages.info(request, 'Проект успешно создан')
redirect_to = reverse('projects:detail', kwargs={'pk': project.pk}) redirect_to = reverse('projects:detail', kwargs={'pk': project.pk})
return redirect(redirect_to) return redirect(redirect_to)
@ -404,13 +386,13 @@ class CustomerProjectCreateView(BaseMixin, View):
'<p>Произошла ошибка (form)</p>' '<p>Произошла ошибка (form)</p>'
'<pre>{form}</pre>' '<pre>{form}</pre>'
).format(form=pformat(form.errors))) ).format(form=pformat(form.errors)))
if realty_form and realty_form.errors: if realty_form and realty_form.errors:
messages.info(request, ( messages.info(request, (
'<p>Произошла ошибка (realty_form)</p>' '<p>Произошла ошибка (realty_form)</p>'
'<pre>{realty_form}</pre>' '<pre>{realty_form}</pre>'
).format(realty_form=pformat(realty_form.errors))) ).format(realty_form=pformat(realty_form.errors)))
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
context.update({'form': form, 'realty_form': realty_form}) context.update({'form': form, 'realty_form': realty_form})
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -420,52 +402,52 @@ class CustomerProjectEditView(BaseMixin, View):
form_class = CustomerProjectEditForm form_class = CustomerProjectEditForm
realty_form = RealtyForm realty_form = RealtyForm
template_name = 'customer_project_edit.html' template_name = 'customer_project_edit.html'
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_customer(): if request.user.is_authenticated() and request.user.is_customer():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
return HttpResponseForbidden('403 Forbidden') raise PermissionDenied
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
project = get_object_or_404(request.user.projects, pk=kwargs.get('pk')) project = get_object_or_404(request.user.projects, pk=kwargs.get('pk'))
form = self.form_class(instance=project, request=request) form = self.form_class(instance=project, request=request)
realty = project.realty realty = project.realty
if realty: if realty:
realty_form = self.realty_form(instance=project.realty, request=request, prefix='realty_form') realty_form = self.realty_form(instance=project.realty, request=request, prefix='realty_form')
else: else:
realty_form = self.realty_form(request=request, prefix='realty_form') realty_form = self.realty_form(request=request, prefix='realty_form')
context = self.get_context_data(**_.merge({}, request.GET, kwargs)) context = self.get_context_data(**_.merge({}, request.GET, kwargs))
context.update({'form': form, 'realty_form': realty_form}) context.update({'form': form, 'realty_form': realty_form})
return render(request, self.template_name, context) return render(request, self.template_name, context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
project = get_object_or_404(request.user.projects, pk=kwargs.get('pk')) project = get_object_or_404(request.user.projects, pk=kwargs.get('pk'))
form = self.form_class(request.POST, request.FILES, request=request, instance=project) form = self.form_class(request.POST, request.FILES, request=request, instance=project)
form.is_valid() form.is_valid()
realty = form.cleaned_data.get('realty') realty = form.cleaned_data.get('realty')
if realty: if realty:
realty_form = self.realty_form(request.POST, instance=realty, request=request, prefix='realty_form') realty_form = self.realty_form(request.POST, instance=realty, request=request, prefix='realty_form')
else: else:
realty_form = self.realty_form(request.POST, request=request, prefix='realty_form') realty_form = self.realty_form(request.POST, request=request, prefix='realty_form')
if form.is_valid() and realty_form.is_valid(): if form.is_valid() and realty_form.is_valid():
project = form.save(commit=False) project = form.save(commit=False)
project.customer = request.user project.customer = request.user
project.files = form.cleaned_data.get('files') # TODO: Should we somehow get rid of this explicit assignment? project.files = form.cleaned_data.get('files') # TODO: Should we somehow get rid of this explicit assignment?
project.save() project.save()
form.save_m2m() form.save_m2m()
for file in request.FILES.getlist('new_files'): for file in request.FILES.getlist('new_files'):
proj_file = ProjectFile.objects.create(file=file, project=project) proj_file = ProjectFile.objects.create(file=file, project=project)
proj_file.save() proj_file.save()
if realty: if realty:
realty_form.save() realty_form.save()
else: else:
@ -473,10 +455,10 @@ class CustomerProjectEditView(BaseMixin, View):
realty.user = request.user realty.user = request.user
realty.save() realty.save()
realty_form.save_m2m() realty_form.save_m2m()
project.realty = realty # Connect a realty with a project project.realty = realty # Connect a realty with a project
project.save() project.save()
messages.info(request, 'Проект успешно отредактирован') messages.info(request, 'Проект успешно отредактирован')
redirect_to = request.POST.get('next') redirect_to = request.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
@ -486,13 +468,13 @@ class CustomerProjectEditView(BaseMixin, View):
'<p>Произошла ошибка (form)</p>' '<p>Произошла ошибка (form)</p>'
'<pre>{form}</pre>' '<pre>{form}</pre>'
).format(form=pformat(form.errors))) ).format(form=pformat(form.errors)))
if realty_form and realty_form.errors: if realty_form and realty_form.errors:
messages.info(request, ( messages.info(request, (
'<p>Произошла ошибка (realty_form)</p>' '<p>Произошла ошибка (realty_form)</p>'
'<pre>{realty_form}</pre>' '<pre>{realty_form}</pre>'
).format(realty_form=pformat(realty_form.errors))) ).format(realty_form=pformat(realty_form.errors)))
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
context.update({'form': form, 'realty_form': realty_form}) context.update({'form': form, 'realty_form': realty_form})
return render(request, self.template_name, context) return render(request, self.template_name, context)
@ -528,41 +510,41 @@ class ContractorPortfolioTrashView(View):
class CustomerProjectTrashView(View): class CustomerProjectTrashView(View):
form_class = CustomerProjectTrashForm form_class = CustomerProjectTrashForm
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_customer(): if request.user.is_authenticated() and request.user.is_customer():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
return HttpResponseForbidden('403 Forbidden') return HttpResponseForbidden('403 Forbidden')
def post(self, req, *args, **kwargs): def post(self, req, *args, **kwargs):
form = self.form_class(_.merge({}, req.POST, kwargs), req=req) form = self.form_class(_.merge({}, req.POST, kwargs), req=req)
if form.is_valid(): if form.is_valid():
project = form.cleaned_data.get('pk') project = form.cleaned_data.get('pk')
project.state = 'trashed' project.state = 'trashed'
project.save() project.save()
messages.info(req, 'Проект перемещён в корзину') messages.info(req, 'Проект перемещён в корзину')
else: else:
messages.info(req, 'Произошла ошибка: <pre>{msg}</pre>'.format(msg=pformat(form.errors))) messages.info(req, 'Произошла ошибка: <pre>{msg}</pre>'.format(msg=pformat(form.errors)))
redirect_to = req.POST.get('next') redirect_to = req.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
class CustomerProjectRestoreView(View): class CustomerProjectRestoreView(View):
form_class = CustomerProjectRestoreForm form_class = CustomerProjectRestoreForm
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_customer(): if request.user.is_authenticated() and request.user.is_customer():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
return HttpResponseForbidden('403 Forbidden') return HttpResponseForbidden('403 Forbidden')
def post(self, req, *args, **kwargs): def post(self, req, *args, **kwargs):
form = self.form_class(_.merge({}, req.POST, kwargs), req=req) form = self.form_class(_.merge({}, req.POST, kwargs), req=req)
if form.is_valid(): if form.is_valid():
project = form.cleaned_data.get('pk') project = form.cleaned_data.get('pk')
project.state = 'active' project.state = 'active'
@ -571,23 +553,23 @@ class CustomerProjectRestoreView(View):
messages.info(req, 'Проект восстановлен из корзины') messages.info(req, 'Проект восстановлен из корзины')
else: else:
messages.info(req, 'Произошла ошибка: <pre>{msg}</pre>'.format(msg=pformat(form.errors))) messages.info(req, 'Произошла ошибка: <pre>{msg}</pre>'.format(msg=pformat(form.errors)))
redirect_to = req.POST.get('next') redirect_to = req.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
class CustomerProjectDeleteView(View): class CustomerProjectDeleteView(View):
form_class = CustomerProjectDeleteForm form_class = CustomerProjectDeleteForm
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_customer(): if request.user.is_authenticated() and request.user.is_customer():
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
else: else:
return HttpResponseForbidden('403 Forbidden') return HttpResponseForbidden('403 Forbidden')
def post(self, req, *args, **kwargs): def post(self, req, *args, **kwargs):
form = self.form_class(_.merge({}, req.POST, kwargs), req=req) form = self.form_class(_.merge({}, req.POST, kwargs), req=req)
if form.is_valid(): if form.is_valid():
project = form.cleaned_data.get('pk') project = form.cleaned_data.get('pk')
project.state = 'deleted' project.state = 'deleted'
@ -595,7 +577,7 @@ class CustomerProjectDeleteView(View):
messages.info(req, 'Проект удалён навсегда') messages.info(req, 'Проект удалён навсегда')
else: else:
messages.info(req, 'Произошла ошибка: <pre>{msg}</pre>'.format(msg=pformat(form.errors))) messages.info(req, 'Произошла ошибка: <pre>{msg}</pre>'.format(msg=pformat(form.errors)))
redirect_to = req.POST.get('next') redirect_to = req.POST.get('next')
return redirect(redirect_to) return redirect(redirect_to)
@ -672,7 +654,7 @@ class OfferOrderView(View):
return HttpResponseRedirect(redirect_url) return HttpResponseRedirect(redirect_url)
def contractor_portfolio_create(request): def contractor_portfolio_create(request): # TODO: pekopt: shit. rewrite using generic
if request.is_ajax(): if request.is_ajax():
form = PortfolioForm(data=request.POST) form = PortfolioForm(data=request.POST)
# import code; code.interact(local=dict(globals(), **locals())) # import code; code.interact(local=dict(globals(), **locals()))
@ -729,4 +711,11 @@ class PortfolioDelete(DeleteView):
model = Portfolio model = Portfolio
success_url = reverse_lazy('users:contractor-profile') success_url = reverse_lazy('users:contractor-profile')
class PortfolioDetail(DetailView):
model = Portfolio
template_name = 'portfolio_detail.html'
# import code; code.interact(local=dict(globals(), **locals())) # import code; code.interact(local=dict(globals(), **locals()))

@ -1,12 +1,12 @@
<ul class="rettList {{ class_name }}"> <ul class="rettList {{ class_name }}">
<li>Рейтинг: <span> {{ ratings }}</span></li> <li>Рейтинг: <span> {{ ratings }}</span></li>
<li>Безопасные сделки: <span> 0</span></li> <li>Безопасные сделки: <span> {{ deals }}</span></li> {# TODO #}
<li> <li>
<a href="javascript:void(0)"> <a href="javascript:void(0)">
Отзывы: Отзывы:
<span> + 0</span> <span> + {{ reviews_p }}</span>
<small> 0</small> <small> {{ reviews_n }}</small>
<mark> - 0</mark> <mark> - {{ reviews_m }}</mark>
</a> </a>
</li> </li>
</ul> </ul>

@ -1,38 +1,3 @@
{#<!-- Новая вёрстка ---------------------------------------------------->#}
{##}
{# <div class="dashedCol4 dashedCol44">#}
{# <p class="specUser">#}
{# Специализации:#}
{# </p>#}
{# <div class="insetSpec">#}
{# <span>Интерьеры</span>#}
{# <span>2-й</span>#}
{# </div>#}
{# <div class="insetSpec">#}
{# <span>Визуализация/3D</span>#}
{# <span>45-й</span>#}
{# </div>#}
{# <div class="insetSpec">#}
{# <span>Экстерьеры</span>#}
{# <span>10-й</span>#}
{# </div>#}
{# <div class="showSpec">#}
{# <div class="insetSpec">#}
{# <span>Архитектура</span>#}
{# <span>3-й</span>#}
{# </div>#}
{# <div class="insetSpec">#}
{# <span>3D Моделирование</span>#}
{# <span>100-й</span>#}
{# </div>#}
{# </div>#}
{# <button class="showPress">#}
{# #}
{# </button>#}
{# </div>#}
<div class="dashedCol4"> <div class="dashedCol4">
<p class="specUser">Специализации:</p> <p class="specUser">Специализации:</p>
{% for spec in specializations %} {% for spec in specializations %}
@ -40,5 +5,7 @@
<span>{{ spec.specialization.name }}</span> <span>{{ spec.specialization.name }}</span>
<span>{{ spec.position }}-й</span> <span>{{ spec.position }}-й</span>
</div> </div>
{% empty %}
Рейтинги скоро будут рассчитаны
{% endfor %} {% endfor %}
</div> </div>

@ -1,11 +1,13 @@
from django import template from django import template
from archilance import util from projects.models import Order
from users.models import User, Team
from ratings.models import SpecializationRating from ratings.models import SpecializationRating
from reviews.models import Review
from users.models import User, Team
register = template.Library() register = template.Library()
@register.inclusion_tag('templatetags/specializations_widget.html', takes_context=True) @register.inclusion_tag('templatetags/specializations_widget.html', takes_context=True)
def specialization_widget(context, user_id, class_name=None): def specialization_widget(context, user_id, class_name=None):
user_id = int(user_id) user_id = int(user_id)
@ -25,18 +27,26 @@ def specialization_team_widget(context, team_id):
} }
@register.inclusion_tag("templatetags/ratings_widget.html", takes_context=True) @register.inclusion_tag("templatetags/ratings_widget.html", takes_context=True)
def ratings_widget(context, user_id, class_name=None): def ratings_widget(context, user_id, class_name=None):
ratings = User.objects.get(pk=user_id).rating ratings = User.objects.get(pk=user_id).rating
return { return {
'ratings': ratings, 'ratings': ratings,
'class_name': class_name 'class_name': class_name,
'deals': Order.objects.filter(secure=True, contractor_id=user_id, status=1).count(),
'reviews_n': Review.objects.filter(target_contractor_id=user_id, type='neutral').count(),
'reviews_m': Review.objects.filter(target_contractor_id=user_id, type='negative').count(),
'reviews_p': Review.objects.filter(target_contractor_id=user_id, type='positive').count(),
} }
@register.inclusion_tag("templatetags/ratings_widget.html", takes_context=True) @register.inclusion_tag("templatetags/ratings_widget.html", takes_context=True)
def ratings_team_widget(context, team_id): def ratings_team_widget(context, team_id):
ratings = Team.objects.get(pk=team_id).rating ratings = Team.objects.get(pk=team_id).rating
return { return {
'ratings': ratings, 'ratings': ratings,
'deals': Order.objects.filter(secure=True, team_id=team_id, status=1).count(),
'reviews_n': Review.objects.filter(target_team_id=team_id, type='neutral').count(),
'reviews_m': Review.objects.filter(target_team_id=team_id, type='negative').count(),
'reviews_p': Review.objects.filter(target_team_id=team_id, type='positive').count(),
} }

@ -107,12 +107,12 @@
<a href="{% url 'wallets:score-detail' pk=request.user.pk %}">Счет<span></span></a> <a href="{% url 'wallets:score-detail' pk=request.user.pk %}">Счет<span></span></a>
</li> </li>
<li class="icon_mm5"> <li class="icon_mm5">
<a href="#">Настройки<span></span></a> <a href="{% url 'users:user-profile-edit' pk=request.user.pk %}">Настройки<span></span></a>
</li> </li>
<li class="icon_mm6"> {# <li class="icon_mm6">#}
<a href="#">FAQ<span></span></a> {# <a href="#">FAQ<span></span></a>#}
</li> {# </li>#}
<li class="icon_mm6"> <li class="icon_mm6">
<a href="{% url 'auth_logout' %}">Выйти<span></span></a> <a href="{% url 'auth_logout' %}">Выйти<span></span></a>

@ -4,8 +4,10 @@ source ../env/bin/activate &&
pip install -r requirements/base.txt && pip install -r requirements/base.txt &&
git reset --hard && git reset --hard &&
git pull && git pull &&
chmod +x update.sh &&
./manage.py migrate --noinput && ./manage.py migrate --noinput &&
./manage.py collectstatic --noinput && ./manage.py collectstatic --noinput &&
supervisorctl restart arch && supervisorctl restart arch &&
./manage.py recalculation_spec supervisorctl restart arch_chat &&
./manage.py recalculation_spec &&
chown -R www-data:www-data ./ && chmod -R 775 ./ &&
chmod +x update.sh

@ -24,7 +24,7 @@
<div class="titleF1 disTab"> <div class="titleF1 disTab">
<div class="col-lg-3">Специализации</div> <div class="col-lg-3">Специализации</div>
</div> </div>
<div class="polsF1 disTab"> <div class="polsF1 disTab -spec-select-container">
<div class="col-lg-3"> <div class="col-lg-3">
<input type="hidden" class="-spec-select -spec-select-level-1" style="width: 100%"> <input type="hidden" class="-spec-select -spec-select-level-1" style="width: 100%">
</div> </div>
@ -38,34 +38,11 @@
<input type="hidden" class="-spec-select -spec-select-level-4" style="width: 100%"> <input type="hidden" class="-spec-select -spec-select-level-4" style="width: 100%">
</div> </div>
<input type="hidden" id="chosenSpecId" name="{{ form.specialization.html_name }}" value="{{ form.specialization.value }}"> <input type="hidden" name="{{ form.specialization.html_name }}" value="{{ form.specialization.value }}" class="-chosen-spec-id">
</div>
<div class="titleF1 titleF2 disTab">
<div class="col-lg-3">Местоположение</div>
</div> </div>
<div class="searchF1 polsF1 polsFF polsF3"> <div class="searchF1 polsF1 polsFF polsF3">
<div class="col-lg-3">
<input type="hidden" class="-location-select -location-select-country" style="width: 100%">
</div>
<div class="col-lg-3">
<input type="hidden" class="-location-select -location-select-region" style="width: 100%">
</div>
<div class="col-lg-3">
<input type="hidden" class="-location-select -location-select-city" style="width: 100%">
</div>
<input type="hidden" id="chosenLocationId" name="{{ form.location.html_name }}" value="{{ form.location.value }}">
<div class="col-lg-3"> <div class="col-lg-3">
<a href="#" class="findReal" onclick="$(this).closest('form').submit(); return false"> <a href="#" class="findReal" onclick="$(this).closest('form').submit(); return false">
найти исполнителя найти исполнителя
@ -103,6 +80,27 @@
<div class="slideRes disTab activeSlide"> <div class="slideRes disTab activeSlide">
<div class="titleF1 disTab">
<div class="col-lg-12">Местоположение</div>
</div>
<div class="polsF1 disTab">
<div class="col-lg-3">
<input type="hidden" class="-location-select -location-select-country" style="width: 100%">
</div>
<div class="col-lg-3">
<input type="hidden" class="-location-select -location-select-region" style="width: 100%">
</div>
<div class="col-lg-3">
<input type="hidden" class="-location-select -location-select-city" style="width: 100%">
</div>
<input type="hidden" id="chosenLocationId" name="{{ form.location.html_name }}" value="{{ form.location.value }}">
</div>
<div class="titleF1 disTab"> <div class="titleF1 disTab">
<div class="col-lg-3">Классификация здания</div> <div class="col-lg-3">Классификация здания</div>
<div class="col-lg-3">Тип работы</div> <div class="col-lg-3">Тип работы</div>
@ -178,7 +176,7 @@
<img src="img/profile.jpg" alt="execitor-image"> <img src="img/profile.jpg" alt="execitor-image">
</div></a> </div></a>
<p class="nameExecutor"> <p class="nameExecutor">
<a href="#">{{ team.name }}</a> <a href="">{{ team.name }}</a> {# TODO #}
</p> </p>
<p class="navv2">На сайте 8 лет и 3 месяца</p> <p class="navv2">На сайте 8 лет и 3 месяца</p>
<div class="statusUser">Свободен</div> <div class="statusUser">Свободен</div>

@ -91,7 +91,8 @@
<span>Программист</span> <span>Программист</span>
</div> </div>
{% empty %}
В группе пока нет участников
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -163,7 +164,7 @@
</div> </div>
</div> </div>
<div class="insetCol2 box-sizing disTab"> <div class="insetCol2 box-sizing disTab">
<p>{{ p.name }}</p> <a href="{% url 'projects:contractor-portfolio-detail' p.pk %}">{{ p }}</a>
<div class="buttonsImg" disTab> <div class="buttonsImg" disTab>
<a href="{% url 'projects:contractor-portfolio-edit' p.pk %}"> <a href="{% url 'projects:contractor-portfolio-edit' p.pk %}">
<div class="insetBI insetBI1"> <div class="insetBI insetBI1">
@ -176,15 +177,19 @@
</div> </div>
</div> </div>
</div> </div>
{% empty %}
<div class="col-lg-12">
<p style="text-align: center;margin-top: 25px;">Работ в портфолио пока нет</p>
</div>
{% endfor %} {% endfor %}
</div> </div>
{# TODO:#}
<div class="col-lg-9 col-lg-offset-3"> {# <div class="col-lg-9 col-lg-offset-3">#}
<div class="linkElse"> {# <div class="linkElse">#}
<a href="javascript:void(0)" class="showElse">показать еще</a> {# <a href="javascript:void(0)" class="showElse">показать еще</a>#}
</div> {# </div>#}
</div> {# </div>#}
</div> </div>
<div id="tab12" class="tab-pane fade"> <div id="tab12" class="tab-pane fade">
@ -204,42 +209,48 @@
</div> </div>
</div> </div>
<div class="insetCol2 box-sizing disTab"> <div class="insetCol2 box-sizing disTab">
<p>{{ ws }}</p> <a href="{% url 'work_sell:detail' ws.pk %}">{{ ws }}</a>
</div> </div>
</div> </div>
{% empty %}
<div class="col-lg-12">
<p style="text-align: center;margin-top: 25px;">Готовых работ пока нет</p>
</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="col-lg-9 col-lg-offset-3"> {# TODO#}
<div class="linkElse"> {# <div class="col-lg-9 col-lg-offset-3">#}
<a href="javascript:void(0)" class="showElse">показать еще</a> {# <div class="linkElse">#}
</div> {# <a href="javascript:void(0)" class="showElse">показать еще</a>#}
</div> {# </div>#}
{# </div>#}
</div> </div>
<div id="tab13" class="tab-pane fade"> <div id="tab13" class="tab-pane fade">
{% for review in reviews %}
<div class="new-comm-44"> <div class="new-comm-44">
<div class="col-lg-12"> <div class="col-lg-12">
<p class="nameComm"> <p class="nameComm">
<a href="#">Иванов Петр Иванович</a> <a href="">{{ review.get_sender }}</a>
</p> </p>
<span class="dateComm44"> {% if review.project.deal_type == 'secure_deal' %}
Безопасная сделка <span class="dateComm44">
</span> Безопасная сделка
</span>
{% endif %}
<div class="stars box-sizing"> <div class="stars box-sizing">
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
<a href="#">положительный отзыв</a> <a href="#">положительный отзыв</a>
</div> </div>
<p class="textComm44"> <p class="textComm44">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum {{ review.text|safe }}
</p> </p>
</div> </div>
</div> </div>
{% empty %}
<div class="new-comm-44">
<p style="text-align: center;">Отзывов пока нет</p>
</div>
{% endfor %}
</div> </div>
</div> </div>
</div> </div>

@ -176,8 +176,7 @@
{% endthumbnail %} {% endthumbnail %}
</div> </div>
<div class="insetCol2 box-sizing disTab"> <div class="insetCol2 box-sizing disTab">
<p>{{ p.name }}</p> <a href="{% url 'projects:contractor-portfolio-detail' p.pk %}">{{ p }}</a>
{% if request.user.pk == contractor.pk %} {% if request.user.pk == contractor.pk %}
<div class="buttonsImg" disTab> <div class="buttonsImg" disTab>
<a href="{% url 'projects:contractor-portfolio-edit' p.pk %}"> <a href="{% url 'projects:contractor-portfolio-edit' p.pk %}">
@ -196,7 +195,12 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% empty %}
<div class="col-lg-4">
<p style="text-align: center;">Работ в портфолио пока нет</p>
</div>
{% endfor %} {% endfor %}
</div> </div>
<div class="col-lg-9 col-lg-offset-3"> <div class="col-lg-9 col-lg-offset-3">
@ -247,8 +251,7 @@
</div> </div>
</div> </div>
<div class="insetCol2 box-sizing disTab"> <div class="insetCol2 box-sizing disTab">
<p>{{ ws }}</p> <a href="{% url 'work_sell:detail' ws.pk %}">{{ ws }}</a>
{% if request.user.pk == contractor.pk %} {% if request.user.pk == contractor.pk %}
<div class="buttonsImg" disTab> <div class="buttonsImg" disTab>
<div class="insetBI insetBI1"> <div class="insetBI insetBI1">
@ -455,6 +458,10 @@
</p> </p>
</div> </div>
</div> </div>
{% empty %}
<div class="new-comm-44">
<p style="text-align: center;">Отзывов пока нет</p>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

@ -95,9 +95,9 @@
<div class="extAreaBlock2 polsF1 text-nn box-sizing disTab"> <div class="extAreaBlock2 polsF1 text-nn box-sizing disTab">
<p></p> <p></p>
<div class="row"> <div class="row">
<div class="col-lg-6 make-new"> <div class="make-new">
<label>{{ portfolio_form.duplicate }}<span></span></label> <label style="margin-left: 0;">{{ portfolio_form.duplicate }}<span></span></label>
<p>Дублировать проект в готовые работы</p> <p>Дублировать проект в готовые работы</p>
</div> </div>

@ -43,56 +43,65 @@
value="{{ worksell_form.specialization.value }}"> value="{{ worksell_form.specialization.value }}">
</div> </div>
<div class="textAreaBlock2 text-nn box-sizing disTab"> <div class="textAreaBlock2 polsF1 text-nn box-sizing disTab">
<p>Бюджет{{ worksell_form.budget.errors.as_text }}</p> <p>Бюджет{{ worksell_form.budget.errors.as_text }}</p>
<div class="row"> <div class="row">
<div class="col-lg-8"> <div class="col-lg-6">
<input type="text" class="box-sizing" name="{{ worksell_form.budget.html_name }}" value="{{ worksell_form.budget.value }}"> <input type="text" class="box-sizing" name="{{ worksell_form.budget.html_name }}" value="{{ worksell_form.budget.value }}">
</div> </div>
<div class="col-lg-4"> <div class="col-lg-3">
{{ worksell_form.currency}} {{ worksell_form.currency}}
</div> </div>
</div> </div>
</div> </div>
<div class="textAreaBlock2 text-nn box-sizing disTab"> <div class="textAreaBlock2 polsF1 text-nn box-sizing disTab">
<p>Срок выполнения{{ worksell_form.budget.errors.as_text }}</p> <p>Срок выполнения{{ worksell_form.budget.errors.as_text }}</p>
<div class="row"> <div class="row">
<div class="col-lg-8"> <div class="col-lg-6">
<input type="text" class="box-sizing" name="{{ worksell_form.term.html_name }}" value="{{ worksell_form.term.value }}"> <input type="text" class="box-sizing" name="{{ worksell_form.term.html_name }}" value="{{ worksell_form.term.value }}">
</div> </div>
<div class="col-lg-4"> <div class="col-lg-3">
{{ worksell_form.term_type }} {{ worksell_form.term_type }}
</div> </div>
</div> </div>
</div> </div>
<div class="polsF1 polsF2 disTab"> <div class="textAreaBlock2 polsF1 text-nn box-sizing disTab">
<p>Вид строительства</p> <p>Вид строительства</p>
<div class="row">
<div class="col-lg-6">
{{ worksell_form.construction_type}} {{ worksell_form.construction_type}}
</div>
</div>
</div> </div>
<div class="polsF1 polsF2 disTab"> <div class="textAreaBlock2 polsF1 text-nn box-sizing disTab">
<p>Классификация здания</p> <p>Классификация здания</p>
<div class="row">
<div class="col-lg-6">
{{ worksell_form.building_classification}} {{ worksell_form.building_classification}}
</div>
</div>
</div> </div>
<div class="polsF1 polsF2 disTab"> <div class="polsF1 disTab">
<input type="hidden" name="{{ worksell_form.contractor.html_name }}" value="{{ request.user.pk }}" /> <input type="hidden" name="{{ worksell_form.contractor.html_name }}" value="{{ request.user.pk }}" />
</div> </div>
<div class="polsF1 polsF2 disTab"> <div class="polsF1 disTab">
<input type="hidden" name="images-ids" id="upload-files-worksell-pk" /> <input type="hidden" name="images-ids" id="upload-files-worksell-pk" />
</div> </div>
<!-- The fileinput-button span is used to style the file input field as button --> <!-- The fileinput-button span is used to style the file input field as button -->
<span class="btn btn-success fileinput-button"> <div class="textAreaBlock2 polsF1 text-nn box-sizing disTab" style="margin-bottom: 12px;">
<i class="glyphicon glyphicon-plus"></i> <span class="btn btn-success fileinput-button add_file_to_port">
<span>Выберите файлы</span> <span>Выберите файлы</span>
<!-- The file input field used as target for the file upload widget --> <!-- The file input field used as target for the file upload widget -->
<input id="fileupload-worksell" type="file" name="file" multiple> <input id="fileupload-worksell" type="file" name="file" multiple>
</span> </span>
</div>
<br> <br>
<br> <br>
<!-- The global progress bar --> <!-- The global progress bar -->
@ -103,7 +112,7 @@
<div id="files-worksell" class="files"></div> <div id="files-worksell" class="files"></div>
<br> <br>
<div class="searchF1 polsF1 polsFF links-filter"> <div class="polsFF links-filter">
<input class="btn-submit-link" type="submit" value="Разместить проект"> <input class="btn-submit-link" type="submit" value="Разместить проект">
</div> </div>

@ -375,6 +375,7 @@ class ContractorOfficeDetailView(DetailView):
work_sells = WorkSell.objects.filter(contractor__in=user_ids) work_sells = WorkSell.objects.filter(contractor__in=user_ids)
context['portfolios'] = portfolios context['portfolios'] = portfolios
context['work_sells'] = work_sells context['work_sells'] = work_sells
context['reviews'] = Review.objects.filter(target_contractor__in=user_ids)
return context return context

@ -1,27 +1,25 @@
from django.conf import urls, settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Sum from django.db.models import Sum
from django.http import HttpResponse, JsonResponse, HttpResponseForbidden from django.http import HttpResponse, JsonResponse
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import DetailView, CreateView from django.views.generic import DetailView, CreateView
from django.views.generic.base import View from django.views.generic.base import View
from pprint import pprint, pformat
import logging
from users.models import User
from .forms import WithDrawForm, TmpCheckOrderForm, TmpPaymentAvisoForm from .forms import WithDrawForm, TmpCheckOrderForm, TmpPaymentAvisoForm
from .models import InvoiceHistory, WithDraw, Transaction from .models import InvoiceHistory, WithDraw, Transaction
from users.mixins import CheckForUserMixin
from users.models import User
class ScoreDetailView(DetailView): class ScoreDetailView(DetailView):
model = User model = User
template_name = 'score-detail.html' template_name = 'score-detail.html'
context_object_name = 'user_score' context_object_name = 'user_score'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
user_score_balance = InvoiceHistory.objects.filter(user=self.get_object()).aggregate(Sum('sum')) user_score_balance = InvoiceHistory.objects.filter(user=self.get_object()).aggregate(Sum('sum'))
@ -30,7 +28,7 @@ class ScoreDetailView(DetailView):
return context return context
class ScoreView(View): class ScoreView(LoginRequiredMixin, View):
template_name = 'score-detail.html' template_name = 'score-detail.html'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -39,7 +37,7 @@ class ScoreView(View):
user_score = get_object_or_404(User.objects, pk=kwargs.get('pk')) user_score = get_object_or_404(User.objects, pk=kwargs.get('pk'))
current_sum_info = InvoiceHistory.objects.filter(user=user_score).aggregate(Sum('sum')) current_sum_info = InvoiceHistory.objects.filter(user=user_score).aggregate(Sum('sum'))
user_score_balance = current_sum_info['sum__sum'] or 0 user_score_balance = current_sum_info['sum__sum'] or 0
return render(request, self.template_name, { return render(request, self.template_name, {
'transaction': transaction[0], 'transaction': transaction[0],
'YANDEX_MONEY': settings.YANDEX_MONEY, 'YANDEX_MONEY': settings.YANDEX_MONEY,
@ -96,23 +94,22 @@ class WithDrawCreate(CreateView):
return super().form_invalid(form) return super().form_invalid(form)
# Yandex Money ------------------------------------------------ # Yandex Money ------------------------------------------------
class TmpCheckOrderView(View): class TmpCheckOrderView(View):
form_class = TmpCheckOrderForm form_class = TmpCheckOrderForm
@method_decorator(csrf_exempt) @method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.form_class(request.POST) form = self.form_class(request.POST)
# trans = form.cleaned_data.get('transaction') # trans = form.cleaned_data.get('transaction')
if form.is_valid(): if form.is_valid():
res = """<?xml version="1.0" encoding="utf-8"?> res = """<?xml version="1.0" encoding="utf-8"?>
<checkOrderResponse <checkOrderResponse
@ -125,7 +122,7 @@ class TmpCheckOrderView(View):
invoice_id=form.cleaned_data.get('invoiceId'), invoice_id=form.cleaned_data.get('invoiceId'),
shop_id=form.cleaned_data.get('shopId'), shop_id=form.cleaned_data.get('shopId'),
) )
return HttpResponse(res, content_type='application/xml') return HttpResponse(res, content_type='application/xml')
else: else:
res = """<?xml version="1.0" encoding="utf-8"?> res = """<?xml version="1.0" encoding="utf-8"?>
@ -141,21 +138,21 @@ class TmpCheckOrderView(View):
invoice_id=form.cleaned_data.get('invoiceId'), invoice_id=form.cleaned_data.get('invoiceId'),
shop_id=form.cleaned_data.get('shopId'), shop_id=form.cleaned_data.get('shopId'),
) )
return HttpResponse(res, content_type='application/xml') return HttpResponse(res, content_type='application/xml')
# return HttpResponse('<pre>{msg}</pre>'.format(msg=pformat(form.errors))) # Debug # return HttpResponse('<pre>{msg}</pre>'.format(msg=pformat(form.errors))) # Debug
class TmpPaymentAvisoView(View): class TmpPaymentAvisoView(View):
form_class = TmpPaymentAvisoForm form_class = TmpPaymentAvisoForm
@method_decorator(csrf_exempt) @method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
transaction = form.cleaned_data.get('transaction_id') transaction = form.cleaned_data.get('transaction_id')
transaction.complete = True transaction.complete = True
@ -173,7 +170,7 @@ class TmpPaymentAvisoView(View):
invoice_id=form.cleaned_data.get('invoiceId'), invoice_id=form.cleaned_data.get('invoiceId'),
shop_id=form.cleaned_data.get('shopId'), shop_id=form.cleaned_data.get('shopId'),
) )
return HttpResponse(res, content_type='application/xml') return HttpResponse(res, content_type='application/xml')
else: else:
res = """<?xml version="1.0" encoding="utf-8"?> res = """<?xml version="1.0" encoding="utf-8"?>
@ -183,6 +180,6 @@ class TmpPaymentAvisoView(View):
message="Payment aviso, validation error" message="Payment aviso, validation error"
techMessage="Payment aviso, validation error"/> techMessage="Payment aviso, validation error"/>
""".format(date=timezone.now().isoformat()) """.format(date=timezone.now().isoformat())
return HttpResponse(res, content_type='application/xml') return HttpResponse(res, content_type='application/xml')
# return HttpResponse('<pre>{msg}</pre>'.format(msg=pformat(form.errors))) # Debug # return HttpResponse('<pre>{msg}</pre>'.format(msg=pformat(form.errors))) # Debug

@ -48,20 +48,21 @@
<div class="desReadyBlock"> <div class="desReadyBlock">
{% if object.text %}
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
<p class="titleReady">Описание:</p> <p class="titleReady">Описание:</p>
</div> </div>
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
<p class="textReady"> <p class="textReady">
{{ object.text }} {{ object.text }}
</p> </p>
</div> </div>
{% endif %}
<div class="col-lg-12 sliderReady"> <div class="col-lg-12 sliderReady">
<div class="arroww prev3"></div> <div class="arroww prev3"></div>
<div class="arroww next3"></div> <div class="arroww next3"></div>
<div class="swiper-container gallery-top"> <div class="swiper-container gallery-top">
<div class="swiper-wrapper"> <div class="swiper-wrapper">
шь
<div class="swiper-slide" style="background-image:url('{% static object.get_cover.url %}')"></div> <div class="swiper-slide" style="background-image:url('{% static object.get_cover.url %}')"></div>
{% for photo in object.photos.all %} {% for photo in object.photos.all %}
<div class="swiper-slide" style="background-image:url('{% static photo.img.url %}')"></div> <div class="swiper-slide" style="background-image:url('{% static photo.img.url %}')"></div>
@ -77,11 +78,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-12"> {# <div class="col-lg-12">#}
<div class="triangle2"></div> {# <div class="triangle2"></div>#}
<div class="arr arr1"></div> {# <div class="arr arr1"></div>#}
<div class="arr arr2"></div> {# <div class="arr arr2"></div>#}
</div> {# </div>#}
</div> </div>
<div class="col-lg-12 sibl"> <div class="col-lg-12 sibl">
<p>Похожие работы</p> <p>Похожие работы</p>

@ -140,7 +140,7 @@
{% for work in work_sells %} {% for work in work_sells %}
<div class="col-lg-3"> <div class="col-lg-3">
<div class="insetCol box-sizing disTab"> <div class="insetCol box-sizing disTab">
<a href="{% url 'work_sell:detail' work.pk %}"> <a href="{% url 'work_sell:detail' work.pk %}">
{% thumbnail work.get_cover "265x265" crop="center" as im %} {% thumbnail work.get_cover "265x265" crop="center" as im %}
<div class="imgGal" style="background: url('{{ im.url }}') no-repeat center;"> <div class="imgGal" style="background: url('{{ im.url }}') no-repeat center;">
<div class="imgFigure"></div> <div class="imgFigure"></div>
@ -154,8 +154,7 @@
</div> </div>
</div> </div>
<div class="insetCol2 box-sizing disTab"> <div class="insetCol2 box-sizing disTab">
<p>{{ work }}</p> <a href="{% url 'work_sell:detail' work.pk %}">{{ work }}</a>
{% if request.user.pk == work.contractor.pk %} {% if request.user.pk == work.contractor.pk %}
<div class="buttonsImg" disTab> <div class="buttonsImg" disTab>
<a href="{% url 'work_sell:edit' work.pk %}"> <a href="{% url 'work_sell:edit' work.pk %}">

Loading…
Cancel
Save