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 - -+ {{ 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. -
- Иванов Петр Иванович -
- - 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. -
- - Ответить - +- Иванов Петр Иванович -
- - 13.01.2016 / 21:05 - -+ {{ 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. -
- Иванов Петр Иванович -
- - 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. -
- - Ответить - +Произошла ошибка (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')