remotes/origin/PR-39
ArturBaybulatov 10 years ago
parent 3ed593b96c
commit b7db81918c
  1. 51
      assets/index.js
  2. 23
      projects/forms.py
  3. 16
      projects/migrations/0006_merge.py
  4. 33
      projects/migrations/0007_answermessage.py
  5. 25
      projects/migrations/0008_auto_20160815_1900.py
  6. 19
      projects/models.py
  7. 160
      projects/templates/project_detail.html
  8. 9
      projects/urls.py
  9. 55
      projects/views.py

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

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

@ -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 = [
]

@ -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': 'Отклики на проекты -- переписка',
},
),
]

@ -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,
),
]

@ -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)

@ -267,50 +267,52 @@
<div class="commBlock44 disTab">
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
<p class="nameComm">
Иванов Петр Иванович
</p>
<span class="dateComm44">
13.01.2016 / 21:05
</span>
<div class="stars box-sizing">
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
{% for message in answer.messages.all %}
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
{% if message.is_sender_customer %}
<p class="nameComm nameCommAct">
{{ project.customer.get_full_name }}
</p>
{% else %}
<p class="nameComm">
{% if answer.author|class_name == 'User' %}
{{ answer.author.get_full_name }}
{% elif answer.author|class_name == 'Team' %}
{{ answer.author.name }}
{% endif %}
</p>
{% endif %}
<span class="dateComm44">
{{ message.created }}
</span>
<p class="textComm44">
{{ message.text }}
</p>
</div>
<p class="textComm44">
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.
</p>
</div>
</div>
{% endfor %}
<div class="comm44 disTab">
{# <div class="i col-lg-10 col-lg-offset-1">#}
{# <a href="#" onclick="return false" class="answerComm">#}
{# Ответить#}
{# </a>#}
{# </div>#}
<div class="col-lg-10 col-lg-offset-1">
<p class="nameComm nameCommAct">
Иванов Петр Иванович
</p>
<span class="dateComm44">
13.01.2016 / 21:05
</span>
<p class="textComm44">
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.
</p>
<a href="javascript:void(0)" class="answerComm">
Ответить
</a>
<form action="{% url 'projects:create-answer-message' pk=project.pk %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">
<div><textarea name="text"></textarea></div>
<div><button type="submit">Отправить</button></div>
</form>
</div>
</div>
</div>
</div>
</div>
{% else %}
@ -357,16 +359,14 @@
<div class="col-lg-3"></div>
</div>
<div class="polsF1 disTab searchF1">
{% for x in 'abc' %}
<div class="col-lg-3">
<div class="insetCol box-sizing disTab">
<div class="imgGal" s--tyle="background: url('{{ portf.photos.first.img.url }}') no-repeat center">
<div class="imgFigure"></div>
</div>
<div id="answer-portfolio-photo-widget-container" class="polsF1 disTab searchF1">
<div class="col-lg-3 answer-portfolio-photo-widget" style="display: none">
<div class="insetCol box-sizing disTab">
<div class="imgGal answer-portfolio-photo-widget-photo-cont">
<div class="imgFigure"></div>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="polsF1 disTab searchF1">
@ -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 }}
</li>
@ -557,40 +558,49 @@
</div>
<div class="commBlock44 disTab">
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
<p class="nameComm">
Иванов Петр Иванович
</p>
<span class="dateComm44">
13.01.2016 / 21:05
</span>
<div class="stars box-sizing">
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star starAct" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
{% for message in answer.messages.all %}
<div class="comm44 disTab">
<div class="col-lg-10 col-lg-offset-1">
{% if message.is_sender_customer %}
<p class="nameComm nameCommAct">
{{ project.customer.get_full_name }}
</p>
{% else %}
<p class="nameComm">
{% if answer.author|class_name == 'User' %}
{{ answer.author.get_full_name }}
{% elif answer.author|class_name == 'Team' %}
{{ answer.author.name }}
{% endif %}
</p>
{% endif %}
<span class="dateComm44">
{{ message.created }}
</span>
<p class="textComm44">
{{ message.text }}
</p>
</div>
<p class="textComm44">
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.
</p>
</div>
</div>
{% endfor %}
<div class="comm44 disTab">
{# <div class="i col-lg-10 col-lg-offset-1">#}
{# <a href="#" onclick="return false" class="answerComm">#}
{# Ответить#}
{# </a>#}
{# </div>#}
<div class="col-lg-10 col-lg-offset-1">
<p class="nameComm nameCommAct">
Иванов Петр Иванович
</p>
<span class="dateComm44">
13.01.2016 / 21:05
</span>
<p class="textComm44">
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.
</p>
<a href="javascript:void(0)" class="answerComm">
Ответить
</a>
<form action="{% url 'projects:create-answer-message' pk=project.pk %}" method="POST" novalidate>
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">
<div><textarea name="text"></textarea></div>
<div><button type="submit">Отправить</button></div>
</form>
</div>
</div>
</div>

@ -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<pk>\d+)/$', ProjectDetailWithContractorAnswerView.as_view(), name='detail'),
urls.url(r'^(?P<pk>\d+)/$', ProjectDetailWithAnswerView.as_view(), name='detail'),
urls.url(r'^create/$', CustomerProjectCreateView.as_view(), name='customer-project-create'),
urls.url(r'^(?P<pk>\d+)/edit/$', CustomerProjectEditView.as_view(), name='customer-project-edit'),
@ -31,6 +32,8 @@ urlpatterns = [
urls.url(r'^(?P<pk>\d+)/restore/$', CustomerProjectRestoreView.as_view(), name='customer-project-restore'),
urls.url(r'^(?P<pk>\d+)/delete/$', CustomerProjectDeleteView.as_view(), name='customer-project-delete'),
urls.url(r'^(?P<pk>\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<pk>\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'),

@ -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, (
'<p>Произошла ошибка (form)</p>'
'<pre>{form}</pre>'
).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):
'<pre>{realty_form}</pre>'
).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')

Loading…
Cancel
Save