From b7db81918c687119a5ef9f167731889b8c2d23db Mon Sep 17 00:00:00 2001 From: ArturBaybulatov Date: Tue, 16 Aug 2016 01:03:29 +0300 Subject: [PATCH] #ARC-14 --- assets/index.js | 53 +++++- projects/forms.py | 23 ++- projects/migrations/0006_merge.py | 16 ++ projects/migrations/0007_answermessage.py | 33 ++++ .../migrations/0008_auto_20160815_1900.py | 25 +++ projects/models.py | 19 +- projects/templates/project_detail.html | 162 ++++++++++-------- projects/urls.py | 9 +- projects/views.py | 55 ++++-- 9 files changed, 292 insertions(+), 103 deletions(-) create mode 100644 projects/migrations/0006_merge.py create mode 100644 projects/migrations/0007_answermessage.py create mode 100644 projects/migrations/0008_auto_20160815_1900.py diff --git a/assets/index.js b/assets/index.js index db0d0db..9ab9a3c 100644 --- a/assets/index.js +++ b/assets/index.js @@ -318,15 +318,60 @@ $fileUploadContainer.on('click', '.existing-file-remove-btn', function($evt) { // Project answer portfolio selection ---------------------- -//function ...() { -// -//} - +$('input[type="checkbox"].answer-portfolio-select').on('change', function($evt) { + var $that = $(this) + var portfId = $that.val() + + var $container = $('#answer-portfolio-photo-widget-container') + + if ($that.prop('checked')) { + $.ajax({url: '/api/portfolios/' + portfId + '/', method: 'GET', dataType: 'json'}) + .then(function(portf) { + var photoUrl = portf.photos[0].img + var $newWidget = $container.find('.answer-portfolio-photo-widget').first().clone() + + $newWidget.data('portf-id', portfId) + + var $newWidgetPhotoCont = $newWidget.find('.answer-portfolio-photo-widget-photo-cont').first() + $newWidgetPhotoCont.css('background', 'url("' + photoUrl + '") no-repeat center') + + $container.append($newWidget) + + $newWidget.css('display', 'block') + }) + } else { + var $widgets = $container.find('.answer-portfolio-photo-widget') + var $widget = _.find(function(el) {return $(el).data('portf-id') === portfId}, $widgets) + $widget.remove() + } +}) +var initialPortfIds = $('input[type="checkbox"]:checked.answer-portfolio-select').map(function(i, el) {return $(el).val()}) +loadAllPhotos(initialPortfIds) +function loadAllPhotos(portfIds) { + var $container = $('#answer-portfolio-photo-widget-container') + + _.each(function(portfId) { + $.ajax({url: '/api/portfolios/' + portfId + '/', method: 'GET', dataType: 'json'}) + .then(function(portf) { + var photoUrl = portf.photos[0].img + var $newWidget = $container.find('.answer-portfolio-photo-widget').first().clone() + + $newWidget.data('portf-id', portfId) + + var $newWidgetPhotoCont = $newWidget.find('.answer-portfolio-photo-widget-photo-cont').first() + $newWidgetPhotoCont.css('background', 'url("' + photoUrl + '") no-repeat center') + + $container.append($newWidget) + + $newWidget.css('display', 'block') + }) + }, portfIds) +} diff --git a/projects/forms.py b/projects/forms.py index c3ce34e..90866f6 100644 --- a/projects/forms.py +++ b/projects/forms.py @@ -5,8 +5,9 @@ from mptt.forms import TreeNodeChoiceField from pprint import pprint, pformat import itertools -from .models import Project, ProjectFile, Portfolio, Answer, Realty, PortfolioPhoto, Stage, Specialization +from .models import Project, ProjectFile, Portfolio, Answer, AnswerMessage, Realty, PortfolioPhoto, Stage from common.models import Location +from specializations.models import Specialization from users.models import User @@ -148,10 +149,6 @@ class RealtyForm(forms.ModelForm): # self.fields['location'].queryset = Location.objects # Migrate with this enabled -class Realty1Form(forms.Form): - pass - - class PortfolioForm(forms.ModelForm): class Meta: model = Portfolio @@ -166,6 +163,8 @@ class PortfolioForm(forms.ModelForm): class ProjectAnswerForm(forms.ModelForm): + text = forms.CharField(widget=forms.Textarea) + class Meta: model = Answer @@ -176,7 +175,6 @@ class ProjectAnswerForm(forms.ModelForm): 'secure_deal_only', 'term', 'term_type', - 'text', ) widgets = { @@ -203,6 +201,19 @@ class ProjectAnswerForm(forms.ModelForm): self.fields['portfolios'].queryset = self.request.user.portfolios.all() +class ProjectAnswerMessageForm(forms.ModelForm): + class Meta: + model = AnswerMessage + + fields = ( + 'text', + ) + + def __init__(self, *args, **kwargs): + self.request = kwargs.pop('request') + super().__init__(*args, **kwargs) + + class StageForm(forms.ModelForm): class Meta: model = Stage diff --git a/projects/migrations/0006_merge.py b/projects/migrations/0006_merge.py new file mode 100644 index 0000000..d2c43ca --- /dev/null +++ b/projects/migrations/0006_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-15 08:24 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0005_auto_20160812_1931'), + ('projects', '0005_auto_20160812_1956'), + ] + + operations = [ + ] diff --git a/projects/migrations/0007_answermessage.py b/projects/migrations/0007_answermessage.py new file mode 100644 index 0000000..be2036f --- /dev/null +++ b/projects/migrations/0007_answermessage.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-15 14:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('projects', '0006_merge'), + ] + + operations = [ + migrations.CreateModel( + name='AnswerMessage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('is_sender_customer', models.BooleanField(default=False)), + ('object_id', models.IntegerField()), + ('answer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='projects.Answer')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ], + options={ + 'verbose_name_plural': 'Отклики на проекты -- переписки', + 'verbose_name': 'Отклики на проекты -- переписка', + }, + ), + ] diff --git a/projects/migrations/0008_auto_20160815_1900.py b/projects/migrations/0008_auto_20160815_1900.py new file mode 100644 index 0000000..2900add --- /dev/null +++ b/projects/migrations/0008_auto_20160815_1900.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-15 16:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0007_answermessage'), + ] + + operations = [ + migrations.RemoveField( + model_name='answer', + name='text', + ), + migrations.AddField( + model_name='answermessage', + name='text', + field=models.TextField(default=''), + preserve_default=False, + ), + ] diff --git a/projects/models.py b/projects/models.py index 169ec72..2abce6c 100644 --- a/projects/models.py +++ b/projects/models.py @@ -126,7 +126,6 @@ class Answer(models.Model): secure_deal_only = models.BooleanField(default=False) term = models.IntegerField(default=0) term_type = models.CharField(max_length=10, choices=TERMS, default='hour') - text = models.TextField() content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team')) object_id = models.IntegerField() @@ -141,6 +140,24 @@ class Answer(models.Model): ordering = ('-created',) +class AnswerMessage(models.Model): + answer = models.ForeignKey(Answer, related_name='messages') + created = models.DateTimeField(default=timezone.now) + is_sender_customer = models.BooleanField(default=False) + text = models.TextField() + + content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team')) + object_id = models.IntegerField() + contractor_or_team = GenericForeignKey('content_type', 'object_id') + + def __str__(self): + return str(self.pk) + + class Meta: + verbose_name = 'Отклики на проекты -- переписка' + verbose_name_plural = 'Отклики на проекты -- переписки' + + class AnswerFile(models.Model): answer = models.ForeignKey(Answer, related_name='files', blank=True, null=True) name = models.CharField(max_length=255) diff --git a/projects/templates/project_detail.html b/projects/templates/project_detail.html index 45a9d37..5ea634e 100644 --- a/projects/templates/project_detail.html +++ b/projects/templates/project_detail.html @@ -267,50 +267,52 @@
-
-
-

- Иванов Петр Иванович -

- - 13.01.2016 / 21:05 - -
- - - - - + {% 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 }} +

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum laoreet. Proin gravida dolor sit amet lacus accumsan et viverra justo commodo. Proin sodales pulvinar tempor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam fermentum, nulla luctus pharetra vulputate, felis tellus mollis orci, sed rhoncus sapien nunc eget odio. -

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

- Иванов Петр Иванович -

- - 13.01.2016 / 21:05 - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum laoreet. Proin gravida dolor sit amet lacus accumsan et viverra justo commodo. Proin sodales pulvinar tempor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam fermentum, nulla luctus pharetra vulputate, felis tellus mollis orci, sed rhoncus sapien nunc eget odio. -

- - Ответить - +
+ {% csrf_token %} + + +
+
+
- - -
{% else %} @@ -356,17 +358,15 @@
- -
- {% for x in 'abc' %} -
-
-
-
-
+ +
+
@@ -380,7 +380,8 @@ type="checkbox" name="{{ form.portfolios.html_name }}" value="{{ p.pk }}" - {% if p.pk|str in form.portfolios.value %}checked{% endif %}> + {% if p.pk|str in form.portfolios.value %}checked{% endif %} + class="answer-portfolio-select"> {{ p.name }} @@ -557,40 +558,49 @@
-
-
-

- Иванов Петр Иванович -

- - 13.01.2016 / 21:05 - -
- - - - - + {% 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 }} +

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum laoreet. Proin gravida dolor sit amet lacus accumsan et viverra justo commodo. Proin sodales pulvinar tempor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam fermentum, nulla luctus pharetra vulputate, felis tellus mollis orci, sed rhoncus sapien nunc eget odio. -

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

- Иванов Петр Иванович -

- - 13.01.2016 / 21:05 - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum laoreet. Proin gravida dolor sit amet lacus accumsan et viverra justo commodo. Proin sodales pulvinar tempor. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam fermentum, nulla luctus pharetra vulputate, felis tellus mollis orci, sed rhoncus sapien nunc eget odio. -

- - Ответить - +
+ {% csrf_token %} + + +
+
+
diff --git a/projects/urls.py b/projects/urls.py index 3325fc0..d219e03 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -3,7 +3,6 @@ from django.views.generic import TemplateView from .views import ( add_candidate, - sort_candidates, contractor_portfolio_create, ContractorPortfolioTrashView, ContractorPortfolioUpdateView, @@ -13,16 +12,18 @@ from .views import ( CustomerProjectRestoreView, CustomerProjectTrashView, OfferOrderView, + ProjectAnswerCreateMessageView, ProjectComparisonView, - ProjectDetailWithContractorAnswerView, + ProjectDetailWithAnswerView, ProjectFilterView, + sort_candidates, ) app_name = 'projects' urlpatterns = [ urls.url(r'^$', ProjectFilterView.as_view(), name='project-filter'), - urls.url(r'^(?P\d+)/$', ProjectDetailWithContractorAnswerView.as_view(), name='detail'), + urls.url(r'^(?P\d+)/$', ProjectDetailWithAnswerView.as_view(), name='detail'), urls.url(r'^create/$', CustomerProjectCreateView.as_view(), name='customer-project-create'), urls.url(r'^(?P\d+)/edit/$', CustomerProjectEditView.as_view(), name='customer-project-edit'), @@ -31,6 +32,8 @@ urlpatterns = [ urls.url(r'^(?P\d+)/restore/$', CustomerProjectRestoreView.as_view(), name='customer-project-restore'), urls.url(r'^(?P\d+)/delete/$', CustomerProjectDeleteView.as_view(), name='customer-project-delete'), + urls.url(r'^(?P\d+)/create-answer-message/$', ProjectAnswerCreateMessageView.as_view(), name='create-answer-message'), + 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 1622c0a..116be52 100644 --- a/projects/views.py +++ b/projects/views.py @@ -7,7 +7,7 @@ from django.core.files.base import ContentFile from django.db.models import Q from django.http import HttpResponseForbidden, HttpResponseRedirect, HttpResponse, Http404 from django.shortcuts import render, get_object_or_404, redirect -from django.views.generic import ListView, DetailView, CreateView, View, UpdateView, TemplateView, FormView +from django.views.generic import ListView, DetailView, CreateView, DeleteView, View, UpdateView, TemplateView, FormView from django.views.generic.base import ContextMixin from pprint import pprint, pformat import json @@ -18,27 +18,27 @@ _.filter = _.filter_ import re from .mixins import LastAccessMixin -from .models import Project, ProjectFile, Portfolio, PortfolioPhoto, Candidate, Answer, AnswerFile, Realty, Order +from .models import Project, ProjectFile, Portfolio, PortfolioPhoto, Candidate, Answer, AnswerFile, AnswerMessage, Realty, Order from archilance.mixins import BaseMixin from users.models import User, Team from work_sell.models import Picture from .forms import ( ContractorPortfolioTrashForm, - ProjectAnswerForm, CustomerProjectDeleteForm, CustomerProjectEditForm, - CustomerProjectEditForm, CustomerProjectRestoreForm, CustomerProjectTrashForm, PortfolioForm, + ProjectAnswerForm, + ProjectAnswerMessageForm, ProjectFilterForm, ProjectFilterRealtyForm, RealtyForm, ) -class ProjectDetailWithContractorAnswerView(BaseMixin, View): +class ProjectDetailWithAnswerView(BaseMixin, View): form_class = ProjectAnswerForm template_name = 'project_detail.html' @@ -111,6 +111,11 @@ class ProjectDetailWithContractorAnswerView(BaseMixin, View): if len(file.name) <= 255: AnswerFile.objects.create(file=file, name=file.name, answer=answer) + answer_message = AnswerMessage(text=form.cleaned_data.get('text'), is_sender_customer=False) + answer_message.answer = answer + answer_message.contractor_or_team = answer.author + answer_message.save() + messages.info(request, 'Отклик успешно размещён') redirect_to = request.POST.get('next') return redirect(redirect_to) @@ -127,6 +132,36 @@ class ProjectDetailWithContractorAnswerView(BaseMixin, View): return HttpResponseForbidden('403 Forbidden') +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(): + # project = form.save(commit=False) + # project.customer = request.user + # project.save() + # form.save_m2m() + + messages.info(request, 'OK') + else: + if form.errors: + messages.info(request, ( + '

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

' + '
{form}
' + ).format(form=pformat(form.errors))) + + redirect_to = request.POST.get('next') + return redirect(redirect_to) + + class ProjectFilterView(BaseMixin, View): template_name = 'project_filter.html' form_class = ProjectFilterForm @@ -216,7 +251,6 @@ class ProjectFilterView(BaseMixin, View): '
{realty_form}
' ).format(realty_form=pformat(realty_form.errors))) - # import code; code.interact(local=dict(globals(), **locals())) paginator = Paginator(projects.all(), settings.PAGE_SIZE) page = request.GET.get('page') @@ -260,8 +294,7 @@ class CustomerProjectCreateView(BaseMixin, View): 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 = 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') @@ -356,8 +389,7 @@ class CustomerProjectEditView(BaseMixin, View): 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.files = form.cleaned_data.get('files') # TODO: Should we somehow get rid of this explicit assignment? project.save() form.save_m2m() @@ -559,9 +591,6 @@ class ContractorPortfolioUpdateView(UpdateView): return reverse('proje') -from django.views.generic import DeleteView - - class PortfolioDelete(DeleteView): model = Portfolio success_url = reverse_lazy('users:contractor-profile')