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