diff --git a/archilance/util.py b/archilance/util.py index b7feef0..b919d9f 100644 --- a/archilance/util.py +++ b/archilance/util.py @@ -1,9 +1,11 @@ +from django.core import validators from django.shortcuts import _get_queryset from django.utils import timezone from pprint import pprint, pformat import natsort import pydash as _; _.map = _.map_; _.filter = _.filter_ import random +import string def take(coll, n): @@ -89,3 +91,21 @@ def decap(s): raise TypeError('String expected') 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) diff --git a/assets/css/main.css b/assets/css/main.css index 0bf5b8c..23624e2 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1041,7 +1041,7 @@ footer:after { background-position: 0 -26px; } -.rightPro ul li:last-child:before { +.rightPro ul li:nth-child(3):before { height: 22px; background: url('../img/cenaList.png') no-repeat left; background-size: cover; @@ -5639,13 +5639,13 @@ input[type="radio"]:checked + span { right: 3px; top: 16px; } -#portfolio-add-form .textAreaBlock2 p{ +#portfolio-add-form .textAreaBlock2 p, #worksell-add-form .textAreaBlock2 p{ font-size: 15px; font-family: 'Arial-MT-Regular', sans-serif; font-weight: bold; color: #2c2c2c; } -#portfolio-add-form #text-new{ +#portfolio-add-form #text-new, #worksell-add-form #text-new{ margin-top: 0; } .textAreaBlock2 input{ @@ -5657,10 +5657,10 @@ input[type="radio"]:checked + span { color: #9c9c9c; 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; } -#portfolio-add-form .bootstrap-select{ +#portfolio-add-form .bootstrap-select, #worksell-add-form .bootstrap-select{ width: 100% !important; } .textAreaBlock2 .add_file_to_port{ @@ -5750,7 +5750,7 @@ input[type="radio"]:checked + span { margin: 4px 0 4px 0; } .birth_edit_dat .bootstrap-select:not([class*="col-"]):not([class*="form-control"]):not(.input-group-btn){ - width: 190px; + width: 190px !important; } .btn-submit-link{ color: #333 !important; @@ -5758,4 +5758,65 @@ input[type="radio"]:checked + span { border-color: #ccc !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*/ \ No newline at end of file diff --git a/assets/js/chat.js b/assets/js/chat.js index 5430d7f..d8e0883 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -1,6 +1,6 @@ var SocketHandler = function () { domain = domain.replace(':' + port, ''); - var url = 'ws://' + domain + ':8888/chat/' + userId + '/'; + var url = 'ws://' + domain + '/chat/' + userId + '/'; var sock = new WebSocket(url); var intervalId; sock.onopen = function () { diff --git a/assets/js/main.js b/assets/js/main.js index 29edb85..b623a9a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -3,9 +3,14 @@ $(document).ready(function(){ $('.messd:first').addClass('mesAct'); $('.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); $('.resButtonF1').css('transform','rotate(180deg)'); + $('#extraFields').val('') } $('.resButtonF1').click(function(e){ @@ -15,10 +20,12 @@ $(document).ready(function(){ $('.slideRes').slideUp(300); $(this).css('transform','rotate(180deg)'); $.cookie('slideResVisible', '', {expires: new Date(new Date().getTime() + 300000)}) // 5 minutes + $('#extraFields').val('') } else { $('.slideRes').slideDown(300); $(this).css('transform','rotate(0deg)'); $.cookie('slideResVisible', 'on', {expires: new Date(new Date().getTime() + 300000)}) + $('#extraFields').val('on') } }); diff --git a/chat/templates/chat_contractor.html b/chat/templates/chat_contractor.html index af98f98..c76cb06 100644 --- a/chat/templates/chat_contractor.html +++ b/chat/templates/chat_contractor.html @@ -47,7 +47,13 @@ {% endthumbnail %}

-

{{ contact.username }}
+ {% 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 %} + +
{{ contact.username }}

Контакты diff --git a/chat/templates/chat_customer.html b/chat/templates/chat_customer.html index 70e5022..c447189 100644 --- a/chat/templates/chat_customer.html +++ b/chat/templates/chat_customer.html @@ -41,7 +41,12 @@ {% endif %}

-

{{ contact.username }}
+ {% 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 %} +
{{ contact.username }}

- +
- - - - + diff --git a/projects/templates/customer_project_edit.html b/projects/templates/customer_project_edit.html index fac5262..7d73ac9 100644 --- a/projects/templates/customer_project_edit.html +++ b/projects/templates/customer_project_edit.html @@ -13,7 +13,9 @@
{% csrf_token %} + +
@@ -92,22 +94,22 @@
- +
- +
- +
- +
- +
Бюджет {{ form.budget.errors.as_text }}
@@ -258,19 +260,19 @@
-
Местоположение:
+
Местоположение {{ realty_form.location.errors.as_text }}
- +
- +
- +
@@ -288,17 +290,11 @@

Требуется допуск СРО

- - - - - - - +
diff --git a/projects/templates/portfolio_detail.html b/projects/templates/portfolio_detail.html new file mode 100644 index 0000000..8d4ae53 --- /dev/null +++ b/projects/templates/portfolio_detail.html @@ -0,0 +1,151 @@ +{% extends 'partials/base.html' %} +{% load staticfiles %} +{% load thumbnail %} +{% block content %} + {% include 'partials/header.html' %} +
+
+
+

{{ object }}

+ + {% if object.get_prev %} + + {% endif %} + + {% if object.get_next %} + + {% endif %} +
+ {#
#} + {#
#} + {#
#} + {#

{{ object.budget }}

#} + {#
#} + {# {% if request.user.is_authenticated %}#} + {# #} + {##} + {# #} + {# {% endif %}#} + {# #} + {#
#} + + +
+ {% if object.text %} +
+

Описание:

+
+
+

+ {{ object.text }} +

+
+ {% endif %} +
+
+
+ + +
+{#
#} +{#
#} +{#
#} +{#
#} +{#
#} +
+{#
#} +{#

Похожие работы

#} +{#
#} +{#
#} +{# {% for worksell in worksell_related %}#} +{#
#} +{##} +{#
#} +{# {% thumbnail worksell.get_cover "265x265" crop="center" as im %}#} +{#
#} +{#
#} +{#
#} +{# {% endthumbnail %}#} +{#
#} +{#
#} +{# {% endfor %}#} +{#
#} + + {% include 'partials/footer.html' %} + +
+
+ +{% endblock %} +{% block js_block %} + + +{% endblock %} diff --git a/projects/templates/project_detail.html b/projects/templates/project_detail.html index 2dcd2a3..91bb37f 100644 --- a/projects/templates/project_detail.html +++ b/projects/templates/project_detail.html @@ -3,27 +3,28 @@ {% load humanize %} {% load thumbnail %} {% load specializtions_tags %} +{% load project_tags %} {% block content %} {% include 'partials/header.html' %}
-
-

{{ project.name }}

-
- -
-
- - {% if request.user.is_contractor %} +
+

{{ project.name }}

+
+ +
+
+ + {% if request.user.is_contractor %}

{{ project.budget|intcomma }}

{% endif %} - -
- + + - + +

+ {{ project.customer.get_full_name }} [{{ project.customer.username }}] +

+ + + {% if not request.user.is_contractor %} + + {% endif %} +
+ + {% if request.user.is_contractor %}
  • Рейтинг: 0
  • @@ -51,43 +65,48 @@
- - -
-
    -
  • {{ project.created }}
  • -
  • {{ project.get_work_type_display }}
  • -
+ {% endif %} + +
+
    +
  • + {{ project.created }} +
  • +
  • + {{ project.get_work_type_display }} +
  • +
+ {% if project.cro %}

Есть допуск СРО

{% endif %} -
-
- -
-
- - {% if project.realty %} +
+
+ +
+
+ + {% if project.realty %}
  • Местоположение: {{ project.realty.location.name }}
  • Классификация здания: {{ project.realty.building_classification.name }}
  • -
  • Вид строительства: {{ project.realty.type_construction.name }}
  • +
  • Вид строительства: {{ project.realty.construction_type.name }}
- {% endif %} - -
-

+ {% endif %} + +

+

{{ project.text }}

-
- -
- {% if request.user.is_contractor and not answer %} +
+ +
+ {% if request.user.is_contractor and not answer %} Ответить на проект @@ -109,7 +128,7 @@
{% endif %} - {% elif request.user.is_customer %} + {% elif request.user.is_customer and request.user == project.customer %} Редактировать @@ -123,8 +142,28 @@ {% endif %} -
-
+
+
+ + + + + + + + + + + + + + + + + + + + @@ -137,12 +176,6 @@ {% if request.user.is_contractor %} - - - - - - {% if answer %}
@@ -212,7 +245,7 @@

Опубликован: {{ answer.created|date:'M d, Y' }}

-
+ {% csrf_token %} отказ @@ -262,7 +295,7 @@

- {{ message.text }} + {{ message.text|linebreaksbr }}

@@ -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 %}

Исполнители

-
- - - +
- - {% if project.answers.exists %} {% if project.candidates.count > 1 %}
@@ -458,168 +506,438 @@
{% endif %} -
-

Новые исполнители

-
- - {% for answer in project.answers.all %} -
-
-
- -
- {% if answer.author.avatar %} - execitor-image - {% else %} - execitor-image +
+
+
+

Новые исполнители

+
+ + {% for answer in project|get_new_answers %} +
+
+
+ +
+ {% if answer.author.avatar %} + execitor-image + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {% if answer.author|class_name == 'User' %} + {{ answer.author.get_full_name }} [{{ answer.author.username }}] + {% elif answer.author|class_name == 'Team' %} + {{ answer.author.name }} + {% endif %} +

+ + + + {% if answer.author|class_name == 'User' %} + {% if answer.author.contractor_status == 'free' %} +
Свободен
+ {% endif %} {% endif %}
- - -

- {% if answer.author|class_name == 'User' %} - {{ answer.author.get_full_name }} [{{ answer.author.username }}] - {% elif answer.author|class_name == 'Team' %} - {{ answer.author.name }} - {% endif %} -

- - - - {% if answer.author|class_name == 'User' %} - {% if answer.author.contractor_status == 'free' %} -
Свободен
- {% endif %} - {% endif %} -
- -
- {% ratings_widget answer.author.pk 'restList2' %} - - {% if answer.author|class_name == 'User' and answer.author.cro %} -
-
-

Есть допуск СРО

+ +
+ {% ratings_widget answer.author.pk 'restList2' %} + + {% if answer.author|class_name == 'User' and answer.author.cro %} +
+
+

Есть допуск СРО

+
+ {% elif answer.author|class_name == 'Team' %} + + +
+
+

Есть допуск СРО

+
+ {% endif %}
- {% elif answer.author|class_name == 'Team' %} - -
-
-

Есть допуск СРО

+
+

+ Цена: + {{ answer.budget|intcomma }} + +

+

+ Срок: {{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }} +

+

Опубликован: {{ answer.created|date:'M d, Y' }}

- {% endif %} -
- -
-

- Цена: - {{ answer.budget|intcomma }} - -

-

- Срок: {{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }} -

-

Опубликован: {{ answer.created|date:'M d, Y' }}

-
- -
- - Кандидат - - - - - {% csrf_token %} - - предложить проект - - - -
- {% csrf_token %} - - отказ -
-
- -
- {% for portf in answer.portfolios.all %} -
-
-
-
+ +
+ {% if answer.author not in project|get_candidates %} + + Кандидат + + {% endif %} + + {% if not project.order.contractor and not project.order.team %} +
+ {% csrf_token %} + + + предложить проект + +
+ {% endif %} + +
+ {% csrf_token %} + + отказ +
+
+ +
+ {% for portf in answer.portfolios.all %} +
+
+
+
+
+
+
+ {% endfor %} +
+ +
+ {% for message in answer.messages.all %} +
+
+ {% if message.is_sender_customer %} +

+ {{ project.customer.get_full_name }} +

+ {% else %} +

+ {% if answer.author|class_name == 'User' %} + {{ answer.author.get_full_name }} + {% elif answer.author|class_name == 'Team' %} + {{ answer.author.name }} + {% endif %} +

+ {% endif %} + + + {{ message.created }} + + +

+ {{ message.text|linebreaksbr }} +

+
+
+ {% endfor %} + +
+ + +
+
+ {% csrf_token %} + + +
+
+
- {% endfor %} +
- -
- {% for message in answer.messages.all %} -
-
- {% if message.is_sender_customer %} -

- {{ project.customer.get_full_name }} -

- {% else %} -

- {% if answer.author|class_name == 'User' %} - {{ answer.author.get_full_name }} - {% elif answer.author|class_name == 'Team' %} - {{ answer.author.name }} - {% endif %} -

+ {% endfor %} +
+ + + + + +
+
+

Кандидаты

+
+ + {% for answer in project|get_candidate_answers %} +
+
+
+ +
+ {% if answer.author.avatar %} + execitor-image + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {% if answer.author|class_name == 'User' %} + {{ answer.author.get_full_name }} [{{ answer.author.username }}] + {% elif answer.author|class_name == 'Team' %} + {{ answer.author.name }} {% endif %} +

+ + + + {% if answer.author|class_name == 'User' %} + {% if answer.author.contractor_status == 'free' %} +
Свободен
+ {% endif %} + {% endif %} +
+ +
+ {% ratings_widget answer.author.pk 'restList2' %} + + {% if answer.author|class_name == 'User' and answer.author.cro %} +
+
+

Есть допуск СРО

+
+ {% elif answer.author|class_name == 'Team' %} + - - {{ message.created }} - - -

- {{ message.text }} -

-
+
+
+

Есть допуск СРО

+
+ {% endif %}
- {% endfor %} - -
-{# #} -
-
+
+

+ Цена: + {{ answer.budget|intcomma }} + +

+

+ Срок: {{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }} +

+

Опубликован: {{ answer.created|date:'M d, Y' }}

+
+ +
+ {% if not project.order.contractor and not project.order.team %} + + {% csrf_token %} + + + предложить проект + + + {% endif %} + +
{% csrf_token %} - - -
-
+ + отказ
+ +
+ {% for portf in answer.portfolios.all %} +
+
+
+
+
+
+
+ {% endfor %} +
+ +
+ {% for message in answer.messages.all %} +
+
+ {% if message.is_sender_customer %} +

+ {{ project.customer.get_full_name }} +

+ {% else %} +

+ {% if answer.author|class_name == 'User' %} + {{ answer.author.get_full_name }} + {% elif answer.author|class_name == 'Team' %} + {{ answer.author.name }} + {% endif %} +

+ {% endif %} + + + {{ message.created }} + + +

+ {{ message.text|linebreaksbr }} +

+
+
+ {% endfor %} + +
+ + +
+
+ {% csrf_token %} + + +
+
+
+
+
+
+ {% endfor %} +
+ + + + + +
+
+

Отказы

+ + + + {% for answer in project|get_rejected_answers %} +
+
+
+ +
+ {% if answer.author.avatar %} + execitor-image + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {% if answer.author|class_name == 'User' %} + {{ answer.author.get_full_name }} [{{ answer.author.username }}] + {% elif answer.author|class_name == 'Team' %} + {{ answer.author.name }} + {% endif %} +

+ + + + {% if answer.author|class_name == 'User' %} + {% if answer.author.contractor_status == 'free' %} +
Свободен
+ {% endif %} + {% endif %} +
+ +
+ {% ratings_widget answer.author.pk 'restList2' %} + + {% if answer.author|class_name == 'User' and answer.author.cro %} +
+
+

Есть допуск СРО

+
+ {% elif answer.author|class_name == 'Team' %} + + +
+
+

Есть допуск СРО

+
+ {% endif %} +
+ +
+

+ Цена: + {{ answer.budget|intcomma }} + +

+

+ Срок: {{ answer.term }} {{ answer.get_currency_display }} {{ answer.get_term_type_display|decap }} +

+

Опубликован: {{ answer.created|date:'M d, Y' }}

+
+ +
+ {% for portf in answer.portfolios.all %} +
+
+
+
+
+
+
+ {% endfor %} +
+ +
+ {% for message in answer.messages.all %} +
+
+ {% if message.is_sender_customer %} +

+ {{ project.customer.get_full_name }} +

+ {% else %} +

+ {% if answer.author|class_name == 'User' %} + {{ answer.author.get_full_name }} + {% elif answer.author|class_name == 'Team' %} + {{ answer.author.name }} + {% endif %} +

+ {% endif %} + + + {{ message.created }} + + +

+ {{ message.text|linebreaksbr }} +

+
+
+ {% endfor %} +
+
+
+ {% endfor %}
- {% endfor %} +
{% endif %} - - - - - - - - - - {% endif %} - - {% include 'partials/footer.html' %}
diff --git a/projects/templates/project_filter.html b/projects/templates/project_filter.html index 37108e9..07aa6af 100644 --- a/projects/templates/project_filter.html +++ b/projects/templates/project_filter.html @@ -5,11 +5,11 @@ {% block content %} {% include 'partials/header.html' %} -
-
-
-

Биржа проектов

-
+
+
+
+

Биржа проектов

+
@@ -50,7 +50,7 @@
-
+
- + @@ -230,11 +230,11 @@
- {% include 'partials/pagination.html' %} + {% include 'partials/pagination.html' %}
- {% include 'partials/footer.html' %} -
-
+ {% include 'partials/footer.html' %} +
+
{% endblock %} diff --git a/projects/templatetags/project_tags.py b/projects/templatetags/project_tags.py new file mode 100644 index 0000000..60af555 --- /dev/null +++ b/projects/templatetags/project_tags.py @@ -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())) diff --git a/projects/urls.py b/projects/urls.py index cf359b9..7b81829 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -3,24 +3,24 @@ from django.views.generic import TemplateView from .views import ( add_candidate, + CandidateDeleteView, contractor_portfolio_create, ContractorAnswerArchiveView, ContractorPortfolioTrashView, ContractorPortfolioUpdateView, - ContractorRejectProjectAnswerView, CustomerProjectCreateView, CustomerProjectDeleteView, CustomerProjectEditView, CustomerProjectRestoreView, CustomerProjectTrashView, - CustomerRejectProjectAnswerView, OfferOrderView, ProjectAnswerCreateMessageView, ProjectComparisonView, ProjectDetailWithAnswerView, ProjectFilterView, + RejectProjectAnswerView, sort_candidates, - CandidateDeleteView, + PortfolioDetail, ) app_name = 'projects' @@ -38,12 +38,12 @@ urlpatterns = [ urls.url(r'^(?P\d+)/delete/$', CustomerProjectDeleteView.as_view(), name='customer-project-delete'), urls.url(r'^create-answer-message/(?P\d+)/$', ProjectAnswerCreateMessageView.as_view(), name='create-answer-message'), - urls.url(r'^contractor-reject-project-answer/(?P\d+)/$', ContractorRejectProjectAnswerView.as_view(), name='contractor-reject-project-answer'), - urls.url(r'^customer-reject-project-answer/(?P\d+)/$', CustomerRejectProjectAnswerView.as_view(), name='customer-reject-project-answer'), + urls.url(r'^reject-project-answer/(?P\d+)/$', RejectProjectAnswerView.as_view(), name='reject-project-answer'), urls.url(r'^portfolio/create/$', contractor_portfolio_create, name='contractor-portfolio-create'), + urls.url(r'^portfolio/(?P\d+)/?$', PortfolioDetail.as_view(), name='contractor-portfolio-detail'), urls.url(r'^portfolio/(?P\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'), - + urls.url(r'^candidate/add/(?P(\d+))/(?P(\d+))/$', add_candidate, name='add-candidate'), urls.url(r'^candidate/delete/(?P(\d+))/$', CandidateDeleteView.as_view(), name='delete-candidate'), urls.url(r'^candidate/comparison/sort/$', sort_candidates, name='comparison-sort'), diff --git a/projects/views.py b/projects/views.py index 034c2da..dc5748a 100644 --- a/projects/views.py +++ b/projects/views.py @@ -2,6 +2,7 @@ from django.conf import settings from django.core.exceptions import PermissionDenied from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin +from django.core.exceptions import PermissionDenied from django.core.files.base import ContentFile from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.urlresolvers import reverse, reverse_lazy @@ -42,78 +43,78 @@ from .forms import ( class ProjectDetailWithAnswerView(BaseMixin, View): form_class = ProjectAnswerForm template_name = 'project_detail.html' - + def get(self, request, *args, **kwargs): context = self.get_context_data(**_.merge({}, request.GET, kwargs)) - + project = get_object_or_404(Project, pk=kwargs.get('pk')) context.update({'project': project}) - + hit_count = HitCount.objects.get_for_object(project) HitCountMixin.hit_count(request, hit_count) - + 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 - + answer = _.first(_.filter(project_answers, lambda a: a.author == contractor)) - + if not answer: try: team = contractor.team except Team.DoesNotExist: team = None - + if team: answer = _.first(_.filter(project_answers, lambda a: a.author == team)) - + context.update({'answer': answer}) - + if not answer: try: contractor.team; context.update({'can_answer_as_team': True}) except Team.DoesNotExist: pass - + if request.GET.get('answer_as_team') == 'on': context.update({'answer_as_team': True}) form = self.form_class(request=request, answer_as_team=True, project=project) else: form = self.form_class(request=request, project=project) - + context.update({'form': form}) - + return render(request, self.template_name, context) - + def post(self, request, *args, **kwargs): if request.user.is_authenticated() and request.user.is_contractor(): context = self.get_context_data(**kwargs) answer_as_team = None project = get_object_or_404(Project, pk=kwargs.get('pk')) # TODO: Does this work? - + if request.POST.get('answer_as_team') == 'on': answer_as_team = True - + if answer_as_team: form = self.form_class(request.POST, request=request, answer_as_team=True, project=project) else: form = self.form_class(request.POST, request=request, project=project) - + project = get_object_or_404(Project, pk=kwargs.get('pk')) context.update({'project': project}) - + if form.is_valid(): answer = form.save(commit=False) answer.project = project answer.author = request.user.team if answer_as_team else request.user answer.save() form.save_m2m() - + for file in request.FILES.getlist('new_files'): if len(file.name) <= 255: AnswerFile.objects.create(file=file, name=file.name, answer=answer) - + message = AnswerMessage(text=form.cleaned_data.get('text'), is_sender_customer=False) message.answer = answer message.contractor_or_team = answer.author message.save() - + messages.info(request, 'Отклик успешно размещён') redirect_to = request.POST.get('next') return redirect(redirect_to) @@ -123,7 +124,7 @@ class ProjectDetailWithAnswerView(BaseMixin, View): '

Произошла ошибка (form)

' '
{form}
' ).format(form=pformat(form.errors))) - + context.update({'form': form}) return render(request, self.template_name, context) else: @@ -132,41 +133,41 @@ class ProjectDetailWithAnswerView(BaseMixin, View): class ProjectAnswerCreateMessageView(BaseMixin, View): form_class = ProjectAnswerMessageForm - + def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated(): return super().dispatch(request, *args, **kwargs) else: return HttpResponseForbidden('403 Forbidden') - + def post(self, request, *args, **kwargs): form = self.form_class(request.POST, request=request) - + if form.is_valid(): message = form.save(commit=False) - + if request.user.is_contractor(): answer = get_object_or_404(Answer, pk=kwargs.get('pk')) - + message.answer = answer message.is_sender_customer = False - + if isinstance(answer.author, User) and answer.author == request.user: message.contractor_or_team = request.user elif isinstance(answer.author, Team): try: team = request.user.team except Team.DoesNotExist: team = None - + if team and answer.author == team: message.contractor_or_team = team elif request.user.is_customer(): answer = get_object_or_404(Answer, pk=kwargs.get('pk')) # TODO: Perform additional checks message.answer = answer message.is_sender_customer = True - + message.save() form.save_m2m() - + messages.info(request, 'Сообщение успешно размещено') else: if form.errors: @@ -174,49 +175,30 @@ class ProjectAnswerCreateMessageView(BaseMixin, View): '

Произошла ошибка (form)

' '
{form}
' ).format(form=pformat(form.errors))) - + redirect_to = request.POST.get('next') return redirect(redirect_to) -class ContractorRejectProjectAnswerView(BaseMixin, View): +class RejectProjectAnswerView(BaseMixin, View): 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) else: - return HttpResponseForbidden('403 Forbidden') - + raise PermissionDenied + def post(self, request, *args, **kwargs): - answer = get_object_or_404(request.user.contractor_answers, pk=kwargs.get('pk')) - - answer.delete() - - messages.info(request, 'Вы успешно отказались от проекта') - - redirect_to = request.POST.get('next') - return redirect(redirect_to) + if request.user.is_contractor(): + answer = get_object_or_404(request.user.contractor_answers, pk=kwargs.get('pk')) + elif request.user.is_customer(): + project = get_object_or_404(request.user.projects, answers__pk=kwargs.get('pk')) + answer = get_object_or_404(project.answers, pk=kwargs.get('pk')) + + answer.rejected = True + 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') return redirect(redirect_to) @@ -225,107 +207,107 @@ class ProjectFilterView(BaseMixin, View): template_name = 'project_filter.html' form_class = ProjectFilterForm realty_form = ProjectFilterRealtyForm - + def get(self, request, *args, **kwargs): form = self.form_class(request.GET, request=request) realty_form = self.realty_form(request.GET, request=request, prefix='realty_form') context = self.get_context_data(**_.merge({}, request.GET, kwargs)) - + projects = Project.objects - + if form.is_valid() and realty_form.is_valid(): ord = None - + keywords = form.cleaned_data.get('keywords') cro = form.cleaned_data.get('cro') work_type = form.cleaned_data.get('work_type') specialization = form.cleaned_data.get('specialization') - + building_classification = realty_form.cleaned_data.get('building_classification') construction_type = realty_form.cleaned_data.get('construction_type') location = realty_form.cleaned_data.get('location') - + if keywords: keywords = tuple(filter(None, re.split(r'\s|,|;', keywords))) - + for k in keywords: projects = projects.filter(Q(name__icontains=k) | Q(text__icontains=k)) - + if cro: projects = projects.filter(cro=cro) - + if work_type: projects = projects.filter(work_type=work_type) - + if specialization: projects = projects.filter( specialization__lft__gte=specialization.lft, specialization__rght__lte=specialization.rght, ) - + if building_classification: projects = projects.filter(realty__building_classification=building_classification) - + if construction_type: projects = projects.filter(realty__construction_type=construction_type) - + if location: projects = projects.filter( realty__location__lft__gte=location.lft, realty__location__rght__lte=location.rght, ) - + order_by = form.cleaned_data.get('order_by') last_order_by = form.cleaned_data.get('last_order_by') reverse_order = form.cleaned_data.get('reverse_order') - + if order_by: reverse_order = not reverse_order if order_by == last_order_by else False ord = order_by last_order_by = ord elif last_order_by: ord = last_order_by - + manual_sort = None - + if ord and ord == 'views': projects = natsort.natsorted(projects.all(), key=lambda p: p.hit_count.hits, reverse=reverse_order) manual_sort = True elif ord: projects = projects.order_by('-%s' % ord if reverse_order else ord) - + context.update({ 'last_order_by': last_order_by, 'reverse_order': reverse_order, }) - + project_count = len(projects) if manual_sort else projects.count() display_msg = 'Найдено %s проектов' % project_count if project_count > 0 else 'Ничего не найдено' else: display_msg = 'Пожалуйста, введите корректные данные' - + if form.errors: messages.info(request, ( '

Произошла ошибка (form)

' '
{form}
' ).format(form=pformat(form.errors))) - + if realty_form and realty_form.errors: messages.info(request, ( '

Произошла ошибка (realty_form)

' '
{realty_form}
' ).format(realty_form=pformat(realty_form.errors))) - + paginator = Paginator(projects if manual_sort else projects.all(), settings.PAGE_SIZE) page = request.GET.get('page') - + try: projects = paginator.page(page) except PageNotAnInteger: projects = paginator.page(1) except EmptyPage: projects = paginator.page(paginator.num_pages) - + context.update({ 'form': form, 'realty_form': realty_form, @@ -334,7 +316,7 @@ class ProjectFilterView(BaseMixin, View): 'page_obj': projects, 'display_msg': display_msg, }) - + return render(request, self.template_name, context) @@ -342,33 +324,33 @@ class CustomerProjectCreateView(BaseMixin, View): form_class = CustomerProjectEditForm realty_form = RealtyForm template_name = 'customer_project_create.html' - + def dispatch(self, request, *args, **kwargs): if request.user.is_authenticated() and request.user.is_customer(): return super().dispatch(request, *args, **kwargs) else: raise PermissionDenied - + def get(self, request, *args, **kwargs): form = self.form_class(request=request) realty_form = self.realty_form(request=request, prefix='realty_form') - + context = self.get_context_data(**_.merge({}, request.GET, kwargs)) context.update({'form': form, 'realty_form': realty_form}) - + return render(request, self.template_name, context) - + 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.is_valid() realty = form.cleaned_data.get('realty') - + if realty: realty_form = self.realty_form(request.POST, instance=realty, request=request, prefix='realty_form') else: realty_form = self.realty_form(request.POST, request=request, prefix='realty_form') - + if form.is_valid() and realty_form.is_valid(): project = form.save(commit=False) project.customer = request.user @@ -378,12 +360,12 @@ class CustomerProjectCreateView(BaseMixin, View): secure = False if 'secure_deal' in project.deal_type: secure = True - + Order.objects.create(project=project, secure=secure) for file in request.FILES.getlist('new_files'): ProjectFile.objects.create(file=file, project=project) - + if realty: realty_form.save() else: @@ -391,10 +373,10 @@ class CustomerProjectCreateView(BaseMixin, View): realty.user = request.user realty.save() realty_form.save_m2m() - + project.realty = realty # Connect a realty with a project project.save() - + messages.info(request, 'Проект успешно создан') redirect_to = reverse('projects:detail', kwargs={'pk': project.pk}) return redirect(redirect_to) @@ -404,13 +386,13 @@ class CustomerProjectCreateView(BaseMixin, View): '

Произошла ошибка (form)

' '
{form}
' ).format(form=pformat(form.errors))) - + if realty_form and realty_form.errors: messages.info(request, ( '

Произошла ошибка (realty_form)

' '
{realty_form}
' ).format(realty_form=pformat(realty_form.errors))) - + context = self.get_context_data(**kwargs) context.update({'form': form, 'realty_form': realty_form}) return render(request, self.template_name, context) @@ -420,52 +402,52 @@ class CustomerProjectEditView(BaseMixin, View): form_class = CustomerProjectEditForm realty_form = RealtyForm template_name = 'customer_project_edit.html' - + 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') + raise PermissionDenied def get(self, request, *args, **kwargs): project = get_object_or_404(request.user.projects, pk=kwargs.get('pk')) form = self.form_class(instance=project, request=request) - + realty = project.realty - + if realty: realty_form = self.realty_form(instance=project.realty, request=request, prefix='realty_form') else: realty_form = self.realty_form(request=request, prefix='realty_form') - + context = self.get_context_data(**_.merge({}, request.GET, kwargs)) context.update({'form': form, 'realty_form': realty_form}) - + return render(request, self.template_name, context) - + def post(self, request, *args, **kwargs): 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.is_valid() realty = form.cleaned_data.get('realty') - + if realty: realty_form = self.realty_form(request.POST, instance=realty, request=request, prefix='realty_form') else: realty_form = self.realty_form(request.POST, request=request, prefix='realty_form') - + if form.is_valid() and realty_form.is_valid(): project = form.save(commit=False) project.customer = request.user project.files = form.cleaned_data.get('files') # TODO: Should we somehow get rid of this explicit assignment? project.save() form.save_m2m() - + for file in request.FILES.getlist('new_files'): proj_file = ProjectFile.objects.create(file=file, project=project) proj_file.save() - + if realty: realty_form.save() else: @@ -473,10 +455,10 @@ class CustomerProjectEditView(BaseMixin, View): realty.user = request.user realty.save() realty_form.save_m2m() - + project.realty = realty # Connect a realty with a project project.save() - + messages.info(request, 'Проект успешно отредактирован') redirect_to = request.POST.get('next') return redirect(redirect_to) @@ -486,13 +468,13 @@ class CustomerProjectEditView(BaseMixin, View): '

Произошла ошибка (form)

' '
{form}
' ).format(form=pformat(form.errors))) - + if realty_form and realty_form.errors: messages.info(request, ( '

Произошла ошибка (realty_form)

' '
{realty_form}
' ).format(realty_form=pformat(realty_form.errors))) - + context = self.get_context_data(**kwargs) context.update({'form': form, 'realty_form': realty_form}) return render(request, self.template_name, context) @@ -528,41 +510,41 @@ class ContractorPortfolioTrashView(View): class CustomerProjectTrashView(View): form_class = CustomerProjectTrashForm - + 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, req, *args, **kwargs): form = self.form_class(_.merge({}, req.POST, kwargs), req=req) - + if form.is_valid(): project = form.cleaned_data.get('pk') project.state = 'trashed' project.save() - + messages.info(req, 'Проект перемещён в корзину') else: messages.info(req, 'Произошла ошибка:
{msg}
'.format(msg=pformat(form.errors))) - + redirect_to = req.POST.get('next') return redirect(redirect_to) class CustomerProjectRestoreView(View): form_class = CustomerProjectRestoreForm - + 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, req, *args, **kwargs): form = self.form_class(_.merge({}, req.POST, kwargs), req=req) - + if form.is_valid(): project = form.cleaned_data.get('pk') project.state = 'active' @@ -571,23 +553,23 @@ class CustomerProjectRestoreView(View): messages.info(req, 'Проект восстановлен из корзины') else: messages.info(req, 'Произошла ошибка:
{msg}
'.format(msg=pformat(form.errors))) - + redirect_to = req.POST.get('next') return redirect(redirect_to) class CustomerProjectDeleteView(View): form_class = CustomerProjectDeleteForm - + 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, req, *args, **kwargs): form = self.form_class(_.merge({}, req.POST, kwargs), req=req) - + if form.is_valid(): project = form.cleaned_data.get('pk') project.state = 'deleted' @@ -595,7 +577,7 @@ class CustomerProjectDeleteView(View): messages.info(req, 'Проект удалён навсегда') else: messages.info(req, 'Произошла ошибка:
{msg}
'.format(msg=pformat(form.errors))) - + redirect_to = req.POST.get('next') return redirect(redirect_to) @@ -672,7 +654,7 @@ class OfferOrderView(View): return HttpResponseRedirect(redirect_url) -def contractor_portfolio_create(request): +def contractor_portfolio_create(request): # TODO: pekopt: shit. rewrite using generic if request.is_ajax(): form = PortfolioForm(data=request.POST) # import code; code.interact(local=dict(globals(), **locals())) @@ -729,4 +711,11 @@ class PortfolioDelete(DeleteView): model = Portfolio 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())) diff --git a/ratings/templates/templatetags/ratings_widget.html b/ratings/templates/templatetags/ratings_widget.html index 9a9305d..e756f47 100644 --- a/ratings/templates/templatetags/ratings_widget.html +++ b/ratings/templates/templatetags/ratings_widget.html @@ -1,12 +1,12 @@ diff --git a/ratings/templates/templatetags/specializations_widget.html b/ratings/templates/templatetags/specializations_widget.html index 65d20f0..1c0261a 100644 --- a/ratings/templates/templatetags/specializations_widget.html +++ b/ratings/templates/templatetags/specializations_widget.html @@ -1,38 +1,3 @@ -{##} -{##} -{#
#} -{#

#} -{# Специализации:#} -{#

#} -{#
#} -{# Интерьеры#} -{# 2-й#} -{#
#} -{#
#} -{# Визуализация/3D#} -{# 45-й#} -{#
#} -{#
#} -{# Экстерьеры#} -{# 10-й#} -{#
#} -{#
#} -{#
#} -{# Архитектура#} -{# 3-й#} -{#
#} -{#
#} -{# 3D Моделирование#} -{# 100-й#} -{#
#} -{#
#} -{# #} -{#
#} - - -

Специализации:

{% for spec in specializations %} @@ -40,5 +5,7 @@ {{ spec.specialization.name }} {{ spec.position }}-й
+ {% empty %} + Рейтинги скоро будут рассчитаны {% endfor %}
diff --git a/ratings/templatetags/specializtions_tags.py b/ratings/templatetags/specializtions_tags.py index 6ef72bb..2659523 100644 --- a/ratings/templatetags/specializtions_tags.py +++ b/ratings/templatetags/specializtions_tags.py @@ -1,11 +1,13 @@ from django import template -from archilance import util -from users.models import User, Team +from projects.models import Order from ratings.models import SpecializationRating +from reviews.models import Review +from users.models import User, Team register = template.Library() + @register.inclusion_tag('templatetags/specializations_widget.html', takes_context=True) def specialization_widget(context, user_id, class_name=None): 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) def ratings_widget(context, user_id, class_name=None): ratings = User.objects.get(pk=user_id).rating return { '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) def ratings_team_widget(context, team_id): ratings = Team.objects.get(pk=team_id).rating return { '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(), } diff --git a/templates/partials/header.html b/templates/partials/header.html index 4624e77..34bd116 100644 --- a/templates/partials/header.html +++ b/templates/partials/header.html @@ -107,12 +107,12 @@ Счет
  • - Настройки + Настройки
  • -
  • - FAQ -
  • +{#
  • #} +{# FAQ#} +{#
  • #}
  • Выйти diff --git a/update.sh b/update.sh index 8674d16..33766f9 100644 --- a/update.sh +++ b/update.sh @@ -4,8 +4,10 @@ source ../env/bin/activate && pip install -r requirements/base.txt && git reset --hard && git pull && -chmod +x update.sh && ./manage.py migrate --noinput && ./manage.py collectstatic --noinput && supervisorctl restart arch && -./manage.py recalculation_spec \ No newline at end of file +supervisorctl restart arch_chat && +./manage.py recalculation_spec && +chown -R www-data:www-data ./ && chmod -R 775 ./ && +chmod +x update.sh \ No newline at end of file diff --git a/users/templates/contractor_filter.html b/users/templates/contractor_filter.html index 3037eab..32817e4 100644 --- a/users/templates/contractor_filter.html +++ b/users/templates/contractor_filter.html @@ -24,7 +24,7 @@
    Специализации
    -
    +
    @@ -38,34 +38,11 @@
    - -
    - - - - -
    -
    Местоположение
    +
    -
    - -
    - -
    - -
    - -
    - -
    - - - - -
    найти исполнителя @@ -103,6 +80,27 @@
    +
    +
    Местоположение
    +
    + +
    +
    + +
    + +
    + +
    + +
    + +
    + + +
    + +
    Классификация здания
    Тип работы
    @@ -178,7 +176,7 @@ execitor-image

    - {{ team.name }} + {{ team.name }} {# TODO #}

    Свободен
    diff --git a/users/templates/contractor_office.html b/users/templates/contractor_office.html index de876f1..a7e23a1 100644 --- a/users/templates/contractor_office.html +++ b/users/templates/contractor_office.html @@ -91,7 +91,8 @@ Программист
    - + {% empty %} + В группе пока нет участников {% endfor %}
    @@ -163,7 +164,7 @@
  • + {% empty %} +
    +

    Работ в портфолио пока нет

    +
    {% endfor %}
    - - +{# TODO:#} +{#
    #} +{# #} +{#
    #}
    @@ -204,42 +209,48 @@
    -

    {{ ws }}

    - + {{ ws }}
    + {% empty %} +
    +

    Готовых работ пока нет

    +
    {% endfor %}
    - +{# TODO#} +{#
    #} +{# #} +{#
    #}
    + {% for review in reviews %}

    - Иванов Петр Иванович + {{ review.get_sender }}

    - - Безопасная сделка - + {% if review.project.deal_type == 'secure_deal' %} + + Безопасная сделка + + {% endif %}

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum - + {{ review.text|safe }}

    + {% empty %} +
    +

    Отзывов пока нет

    +
    + {% endfor %}
    diff --git a/users/templates/contractor_profile.html b/users/templates/contractor_profile.html index 0d69861..89b546f 100644 --- a/users/templates/contractor_profile.html +++ b/users/templates/contractor_profile.html @@ -176,8 +176,7 @@ {% endthumbnail %}
    -

    {{ p.name }}

    - + {{ p }} {% if request.user.pk == contractor.pk %}
    + {% empty %} +
    +

    Работ в портфолио пока нет

    +
    {% endfor %} +
    @@ -247,8 +251,7 @@
    -

    {{ ws }}

    - +
    {{ ws }} {% if request.user.pk == contractor.pk %}
    @@ -455,6 +458,10 @@

    + {% empty %} +
    +

    Отзывов пока нет

    +
    {% endfor %}
    diff --git a/users/templates/portfolio_create_form.html b/users/templates/portfolio_create_form.html index 6dc8dbb..e9ed537 100644 --- a/users/templates/portfolio_create_form.html +++ b/users/templates/portfolio_create_form.html @@ -95,9 +95,9 @@

    -
    +
    - +

    Дублировать проект в готовые работы

    diff --git a/users/templates/worksell_create_form.html b/users/templates/worksell_create_form.html index f5e4b4a..99276c0 100644 --- a/users/templates/worksell_create_form.html +++ b/users/templates/worksell_create_form.html @@ -43,56 +43,65 @@ value="{{ worksell_form.specialization.value }}">
    -
    +

    Бюджет{{ worksell_form.budget.errors.as_text }}

    -
    +
    -
    +
    {{ worksell_form.currency}}
    -
    +

    Срок выполнения{{ worksell_form.budget.errors.as_text }}

    -
    +
    -
    +
    {{ worksell_form.term_type }}
    -
    -

    Вид строительства

    +
    +

    Вид строительства

    +
    +
    {{ worksell_form.construction_type}} +
    +
    -
    +

    Классификация здания

    +
    +
    {{ worksell_form.building_classification}} +
    +
    -
    +
    -
    +
    - - - Выберите файлы - - - +
    + + Выберите файлы + + + +


    @@ -103,7 +112,7 @@

    -