From 6b0bc1593a98ac091c5b6e69a974961acc65bcea Mon Sep 17 00:00:00 2001 From: PekopT Date: Tue, 23 Aug 2016 17:30:52 +0300 Subject: [PATCH 01/10] fixes --- .../templatetags/ratings_widget.html | 8 +-- .../templatetags/specializations_widget.html | 37 +----------- ratings/templatetags/specializtions_tags.py | 18 ++++-- users/templates/contractor_office.html | 58 +++++++++++-------- users/templates/contractor_profile.html | 9 +++ users/views.py | 1 + wallets/views.py | 42 ++++++-------- 7 files changed, 84 insertions(+), 89 deletions(-) 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/users/templates/contractor_office.html b/users/templates/contractor_office.html index de876f1..18e87b8 100644 --- a/users/templates/contractor_office.html +++ b/users/templates/contractor_office.html @@ -91,7 +91,8 @@ Программист - + {% empty %} + В группе пока нет участников {% endfor %} @@ -176,15 +177,19 @@ + {% empty %} +
+

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

+
{% endfor %} - -
- -
+{# TODO:#} +{#
#} +{# #} +{#
#}
@@ -208,38 +213,45 @@
+ {% 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..55c0941 100644 --- a/users/templates/contractor_profile.html +++ b/users/templates/contractor_profile.html @@ -196,7 +196,12 @@ {% endif %} + {% empty %} +
+

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

+
{% endfor %} +
@@ -455,6 +460,10 @@

+ {% empty %} +
+

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

+
{% endfor %} diff --git a/users/views.py b/users/views.py index d87e666..e51ba37 100644 --- a/users/views.py +++ b/users/views.py @@ -375,6 +375,7 @@ class ContractorOfficeDetailView(DetailView): work_sells = WorkSell.objects.filter(contractor__in=user_ids) context['portfolios'] = portfolios context['work_sells'] = work_sells + context['reviews'] = Review.objects.filter(target_contractor__in=user_ids) return context diff --git a/wallets/views.py b/wallets/views.py index e5a11f8..84df34c 100644 --- a/wallets/views.py +++ b/wallets/views.py @@ -1,27 +1,25 @@ -from django.conf import urls, settings +from django.conf import settings from django.contrib import messages +from django.contrib.auth.mixins import LoginRequiredMixin 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.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt from django.views.generic import DetailView, CreateView 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 .models import InvoiceHistory, WithDraw, Transaction -from users.mixins import CheckForUserMixin -from users.models import User class ScoreDetailView(DetailView): model = User template_name = 'score-detail.html' context_object_name = 'user_score' - + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) user_score_balance = InvoiceHistory.objects.filter(user=self.get_object()).aggregate(Sum('sum')) @@ -30,16 +28,15 @@ class ScoreDetailView(DetailView): return context -class ScoreView(View): +class ScoreView(LoginRequiredMixin, View): template_name = 'score-detail.html' def get(self, request, *args, **kwargs): - # transaction = Transaction.objects.get_or_create(customer=request.user, complete=False) - transaction = Transaction.objects.create(customer=request.user,type='add') + transaction = Transaction.objects.create(customer=request.user, type='add') user_score = get_object_or_404(User.objects, pk=kwargs.get('pk')) current_sum_info = InvoiceHistory.objects.filter(user=user_score).aggregate(Sum('sum')) user_score_balance = current_sum_info['sum__sum'] or 0 - + return render(request, self.template_name, { 'transaction': transaction, 'YANDEX_MONEY': settings.YANDEX_MONEY, @@ -96,23 +93,22 @@ class WithDrawCreate(CreateView): return super().form_invalid(form) - # Yandex Money ------------------------------------------------ class TmpCheckOrderView(View): form_class = TmpCheckOrderForm - + @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) - + def post(self, request, *args, **kwargs): form = self.form_class(request.POST) - + # trans = form.cleaned_data.get('transaction') - + if form.is_valid(): res = """ @@ -141,21 +137,21 @@ class TmpCheckOrderView(View): invoice_id=form.cleaned_data.get('invoiceId'), shop_id=form.cleaned_data.get('shopId'), ) - + return HttpResponse(res, content_type='application/xml') # return HttpResponse('
{msg}
'.format(msg=pformat(form.errors))) # Debug class TmpPaymentAvisoView(View): form_class = TmpPaymentAvisoForm - + @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) - + def post(self, request, *args, **kwargs): form = self.form_class(request.POST) - + if form.is_valid(): transaction = form.cleaned_data.get('transaction_id') transaction.complete = True @@ -173,7 +169,7 @@ class TmpPaymentAvisoView(View): invoice_id=form.cleaned_data.get('invoiceId'), shop_id=form.cleaned_data.get('shopId'), ) - + return HttpResponse(res, content_type='application/xml') else: res = """ @@ -183,6 +179,6 @@ class TmpPaymentAvisoView(View): message="Payment aviso, validation error" techMessage="Payment aviso, validation error"/> """.format(date=timezone.now().isoformat()) - + return HttpResponse(res, content_type='application/xml') # return HttpResponse('
{msg}
'.format(msg=pformat(form.errors))) # Debug From 55f13883ba975f5be6a4914e1418fd699aa89b1e Mon Sep 17 00:00:00 2001 From: ArturBaybulatov Date: Tue, 23 Aug 2016 20:10:04 +0300 Subject: [PATCH 02/10] #ARC-14 --- .../migrations/0014_auto_20160823_1912.py | 24 + projects/models.py | 4 +- projects/templates/project_detail.html | 709 ++++++++++++------ projects/templatetags/project_tags.py | 26 + projects/urls.py | 8 +- projects/views.py | 42 +- 6 files changed, 563 insertions(+), 250 deletions(-) create mode 100644 projects/migrations/0014_auto_20160823_1912.py create mode 100644 projects/templatetags/project_tags.py diff --git a/projects/migrations/0014_auto_20160823_1912.py b/projects/migrations/0014_auto_20160823_1912.py new file mode 100644 index 0000000..2011dc6 --- /dev/null +++ b/projects/migrations/0014_auto_20160823_1912.py @@ -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), + ), + ] diff --git a/projects/models.py b/projects/models.py index 4d7ce77..9f4c9b2 100644 --- a/projects/models.py +++ b/projects/models.py @@ -78,7 +78,7 @@ class Project(models.Model, HitCountMixin): ('trashed', 'В корзине'), ('deleted', 'Удален'), ) - + budget = models.DecimalField(max_digits=10, decimal_places=0) budget_by_agreement = models.BooleanField(default=False) created = models.DateTimeField(default=timezone.now) @@ -89,7 +89,6 @@ class Project(models.Model, HitCountMixin): name = models.CharField(max_length=255) price_and_term_required = models.BooleanField(default=False) realty = models.ForeignKey(Realty, blank=True, null=True, related_name='projects') - rejected_answers_count = models.PositiveIntegerField(default=0) specialization = TreeForeignKey(Specialization, related_name='projects') state = models.CharField(default='active', max_length=20, choices=STATES) term = models.IntegerField(default=0) @@ -134,6 +133,7 @@ class Answer(models.Model): term = models.IntegerField(blank=True, null=True) term_type = models.CharField(max_length=10, choices=TERMS, blank=True, null=True) 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')) object_id = models.IntegerField() diff --git a/projects/templates/project_detail.html b/projects/templates/project_detail.html index e4d6105..aec5b6d 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 }}

+
+ + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -164,12 +176,6 @@ {% if request.user.is_contractor %} - - - - - - {% if answer %}
@@ -289,7 +295,7 @@

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

@@ -448,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 %}
@@ -485,164 +506,426 @@
{% 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' }}

-
- - - -
- {% for portf in answer.portfolios.all %} -
-
-
-
+ +
+ {% if answer.author not in project|get_candidates %} + + Кандидат + + {% 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' }}

+
+ +
+ + предложить проект + + + {% 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/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 9044f0e..ac25df9 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -3,24 +3,23 @@ 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, ) app_name = 'projects' @@ -38,8 +37,7 @@ 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+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'), diff --git a/projects/views.py b/projects/views.py index 263309f..1753cd4 100644 --- a/projects/views.py +++ b/projects/views.py @@ -1,6 +1,7 @@ from django.conf import settings 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 @@ -178,43 +179,24 @@ class ProjectAnswerCreateMessageView(BaseMixin, View): 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(): - return super().dispatch(request, *args, **kwargs) - else: - return HttpResponseForbidden('403 Forbidden') - - 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) - - -class CustomerRejectProjectAnswerView(BaseMixin, View): - def dispatch(self, request, *args, **kwargs): - if request.user.is_authenticated() and request.user.is_customer(): + 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_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) + 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.delete() - - project.rejected_answers_count = F('rejected_answers_count') + 1 - project.save() + answer.rejected = True + answer.save() - messages.info(request, 'Вы успешно отказали пользователю в проекте') + messages.info(request, 'Успешный отказ от проекта') redirect_to = request.POST.get('next') return redirect(redirect_to) From 504bd26f47d4a90bbc3c2b8f7afda8ae7faba43e Mon Sep 17 00:00:00 2001 From: ArturBaybulatov Date: Tue, 23 Aug 2016 20:50:17 +0300 Subject: [PATCH 03/10] Bugfix --- projects/templates/project_detail.html | 8 ++++---- projects/views.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/templates/project_detail.html b/projects/templates/project_detail.html index 1752865..91bb37f 100644 --- a/projects/templates/project_detail.html +++ b/projects/templates/project_detail.html @@ -128,7 +128,7 @@
{% endif %} - {% elif request.user.is_customer %} + {% elif request.user.is_customer and request.user == project.customer %} Редактировать @@ -245,7 +245,7 @@

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

-
+ {% csrf_token %} отказ @@ -490,7 +490,7 @@ Кандидаты {{ project|get_candidate_answers|length }} - Отказал {{ project|get_rejected_answers|length }} + Отказы {{ project|get_rejected_answers|length }}
@@ -823,7 +823,7 @@
-

Отказал

+

Отказы

diff --git a/projects/views.py b/projects/views.py index 409e5e5..207e525 100644 --- a/projects/views.py +++ b/projects/views.py @@ -54,7 +54,7 @@ class ProjectDetailWithAnswerView(BaseMixin, View): 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)) From 175d4dcae046ab6eba19c962ce7e299ec7a78a27 Mon Sep 17 00:00:00 2001 From: shishman Date: Wed, 24 Aug 2016 09:41:23 +0300 Subject: [PATCH 04/10] modal forms edited --- assets/css/main.css | 73 ++++++++++++++++++++-- projects/templates/project_filter.html | 22 +++---- users/templates/portfolio_create_form.html | 4 +- users/templates/worksell_create_form.html | 45 +++++++------ 4 files changed, 107 insertions(+), 37 deletions(-) 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/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/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 @@

- - - - - +
diff --git a/users/templates/contractor_filter.html b/users/templates/contractor_filter.html index 3037eab..09af9d0 100644 --- a/users/templates/contractor_filter.html +++ b/users/templates/contractor_filter.html @@ -24,7 +24,7 @@
Специализации
-
+
@@ -38,7 +38,7 @@
- +
From d0c7138a1b0fbd44e7bf1e97196195103178f61d Mon Sep 17 00:00:00 2001 From: PekopT Date: Wed, 24 Aug 2016 13:17:32 +0300 Subject: [PATCH 06/10] fixes --- assets/js/chat.js | 2 +- templates/partials/header.html | 8 ++++---- update.sh | 5 +++-- users/templates/contractor_filter.html | 2 +- users/templates/contractor_profile.html | 3 +-- work_sell/templates/worksells_list.html | 5 ++--- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/assets/js/chat.js b/assets/js/chat.js index 1375c1f..c4478b4 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/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..0b21a85 100644 --- a/update.sh +++ b/update.sh @@ -4,8 +4,9 @@ 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 +./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 09af9d0..a23195d 100644 --- a/users/templates/contractor_filter.html +++ b/users/templates/contractor_filter.html @@ -178,7 +178,7 @@ execitor-image
  • - {{ team.name }} + {{ team.name }} {# TODO #}

    Свободен
    diff --git a/users/templates/contractor_profile.html b/users/templates/contractor_profile.html index 55c0941..8da283c 100644 --- a/users/templates/contractor_profile.html +++ b/users/templates/contractor_profile.html @@ -252,8 +252,7 @@
    -

    {{ ws }}

    - + {{ ws }} {% if request.user.pk == contractor.pk %}
    diff --git a/work_sell/templates/worksells_list.html b/work_sell/templates/worksells_list.html index 708b3ad..ba8f9c8 100644 --- a/work_sell/templates/worksells_list.html +++ b/work_sell/templates/worksells_list.html @@ -140,7 +140,7 @@ {% for work in work_sells %}
    -

    {{ work }}

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

    -

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

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

    -

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

    - +
    From 14478336d0e92d8a48d3a579acd40365285d9725 Mon Sep 17 00:00:00 2001 From: PekopT Date: Wed, 24 Aug 2016 15:47:01 +0300 Subject: [PATCH 09/10] portfolio detail page --- .../migrations/0015_auto_20160824_1538.py | 29 +++ projects/models.py | 15 ++ projects/templates/portfolio_detail.html | 151 ++++++++++++ projects/urls.py | 4 +- projects/views.py | 217 +++++++++--------- users/templates/contractor_office.html | 5 +- users/templates/contractor_profile.html | 3 +- work_sell/templates/worksell_detail.html | 15 +- 8 files changed, 321 insertions(+), 118 deletions(-) create mode 100644 projects/migrations/0015_auto_20160824_1538.py create mode 100644 projects/templates/portfolio_detail.html diff --git a/projects/migrations/0015_auto_20160824_1538.py b/projects/migrations/0015_auto_20160824_1538.py new file mode 100644 index 0000000..f9bd706 --- /dev/null +++ b/projects/migrations/0015_auto_20160824_1538.py @@ -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), + ), + ] diff --git a/projects/models.py b/projects/models.py index 9f4c9b2..bfd5064 100644 --- a/projects/models.py +++ b/projects/models.py @@ -271,11 +271,25 @@ class Portfolio(models.Model): 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) worksell = models.BooleanField(default=False) + created = models.DateTimeField(default=timezone.now) def __str__(self): 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: + ordering = ['-created'] verbose_name = 'Портфолио' verbose_name_plural = 'Портфолио' @@ -284,6 +298,7 @@ class Portfolio(models.Model): return photo and photo.img + class PortfolioPhoto(models.Model): img = models.ImageField(upload_to='projects/portfolio') portfolio = models.ForeignKey(Portfolio, related_name='photos') 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/urls.py b/projects/urls.py index e858b2a..7b81829 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -20,6 +20,7 @@ from .views import ( ProjectFilterView, RejectProjectAnswerView, sort_candidates, + PortfolioDetail, ) app_name = 'projects' @@ -40,8 +41,9 @@ urlpatterns = [ 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 207e525..888e2c0 100644 --- a/projects/views.py +++ b/projects/views.py @@ -43,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.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) @@ -124,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: @@ -133,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: @@ -175,7 +175,7 @@ class ProjectAnswerCreateMessageView(BaseMixin, View): '

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

    ' '
    {form}
    ' ).format(form=pformat(form.errors))) - + redirect_to = request.POST.get('next') return redirect(redirect_to) @@ -186,19 +186,19 @@ class RejectProjectAnswerView(BaseMixin, View): return super().dispatch(request, *args, **kwargs) else: raise PermissionDenied - + def post(self, request, *args, **kwargs): 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, 'Успешный отказ от проекта') - + redirect_to = request.POST.get('next') return redirect(redirect_to) @@ -207,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, @@ -316,7 +316,7 @@ class ProjectFilterView(BaseMixin, View): 'page_obj': projects, 'display_msg': display_msg, }) - + return render(request, self.template_name, context) @@ -324,44 +324,44 @@ 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 project.save() form.save_m2m() - + Order.objects.create(project=project) - + for file in request.FILES.getlist('new_files'): ProjectFile.objects.create(file=file, project=project) - + if realty: realty_form.save() else: @@ -369,10 +369,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) @@ -382,13 +382,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) @@ -398,52 +398,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') - + 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: @@ -451,10 +451,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) @@ -464,13 +464,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) @@ -506,41 +506,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' @@ -549,23 +549,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' @@ -573,7 +573,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) @@ -650,7 +650,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())) @@ -707,4 +707,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/users/templates/contractor_office.html b/users/templates/contractor_office.html index 18e87b8..a7e23a1 100644 --- a/users/templates/contractor_office.html +++ b/users/templates/contractor_office.html @@ -164,7 +164,7 @@
    {% empty %} diff --git a/users/templates/contractor_profile.html b/users/templates/contractor_profile.html index 8da283c..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 %}
    diff --git a/work_sell/templates/worksell_detail.html b/work_sell/templates/worksell_detail.html index b6d7e05..e43e153 100644 --- a/work_sell/templates/worksell_detail.html +++ b/work_sell/templates/worksell_detail.html @@ -48,20 +48,21 @@
    + {% if object.text %}

    Описание:

    {{ object.text }} -

    +

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

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

    From 96f8bd7e95171886a8befd0a8ea751fb7ebee183 Mon Sep 17 00:00:00 2001 From: ArturBaybulatov Date: Wed, 24 Aug 2016 16:26:11 +0300 Subject: [PATCH 10/10] Fixes #ARC-13 #ARC-47 --- archilance/util.py | 20 +++++++++ assets/js/main.js | 8 ++-- projects/forms.py | 32 ++++++++++++++ .../templates/customer_project_create.html | 8 ++-- projects/templates/customer_project_edit.html | 32 ++++++-------- projects/views.py | 2 +- users/templates/contractor_filter.html | 44 +++++++++---------- 7 files changed, 96 insertions(+), 50 deletions(-) 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/js/main.js b/assets/js/main.js index b4802e1..b623a9a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -6,11 +6,11 @@ $(document).ready(function(){ if ($.cookie('slideResVisible')) { $('.slideRes').slideDown(300); $('.resButtonF1').css('transform','rotate(0deg)'); - //$('#extraFields').val('') + $('#extraFields').val('on') } else { $('.slideRes').slideUp(300); $('.resButtonF1').css('transform','rotate(180deg)'); - //$('#extraFields').val('on') + $('#extraFields').val('') } $('.resButtonF1').click(function(e){ @@ -20,12 +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('') + $('#extraFields').val('') } else { $('.slideRes').slideDown(300); $(this).css('transform','rotate(0deg)'); $.cookie('slideResVisible', 'on', {expires: new Date(new Date().getTime() + 300000)}) - //$('#extraFields').val('on') + $('#extraFields').val('on') } }); diff --git a/projects/forms.py b/projects/forms.py index 0771918..e198a85 100644 --- a/projects/forms.py +++ b/projects/forms.py @@ -6,6 +6,7 @@ from pprint import pprint, pformat import itertools from .models import Project, ProjectFile, Portfolio, Answer, AnswerMessage, Realty, PortfolioPhoto, Stage +from archilance import util from common.models import Location from specializations.models import Specialization from users.models import User, Team @@ -117,12 +118,27 @@ class CustomerProjectEditForm(forms.ModelForm): super().__init__(*args, **kwargs) 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 # Migrate with this enabled if self.instance.pk: 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): @@ -145,8 +161,24 @@ class RealtyForm(forms.ModelForm): self.request = kwargs.pop('request') 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 # 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): diff --git a/projects/templates/customer_project_create.html b/projects/templates/customer_project_create.html index 05ee717..63e5e28 100644 --- a/projects/templates/customer_project_create.html +++ b/projects/templates/customer_project_create.html @@ -14,6 +14,8 @@
    {% csrf_token %} + +

    Формирование заказа

    @@ -52,7 +54,7 @@

    Тип работы {{ form.work_type.errors.as_text }}

    - {% for id, name in form.work_type.field.choices %} + {% for id, text in form.work_type.field.choices %}
    -

    {{ name }}

    +

    {{ text }}

    {% endfor %}
    @@ -187,8 +189,6 @@ - -

    Расширенный поиск

    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/views.py b/projects/views.py index 207e525..8aaf324 100644 --- a/projects/views.py +++ b/projects/views.py @@ -403,7 +403,7 @@ class CustomerProjectEditView(BaseMixin, View): 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')) diff --git a/users/templates/contractor_filter.html b/users/templates/contractor_filter.html index 09af9d0..df9cdc1 100644 --- a/users/templates/contractor_filter.html +++ b/users/templates/contractor_filter.html @@ -42,30 +42,7 @@
    - - -
    -
    Местоположение
    -
    - -