remotes/origin/PR-39
ArturBaybulatov 10 years ago
parent 31dc2ccdea
commit adba94ad6e
  1. 8
      assets/index.js
  2. 26
      projects/forms.py
  3. 26
      projects/migrations/0009_auto_20160816_1439.py
  4. 35
      projects/migrations/0010_auto_20160816_1942.py
  5. 18
      projects/models.py
  6. 51
      projects/templates/project_detail.html
  7. 2
      projects/urls.py
  8. 79
      projects/views.py
  9. 2
      templates/partials/base.html

@ -267,6 +267,8 @@ function loadRealtyDetails(realtyId) {
// File uploading --------------------------------------- // File uploading ---------------------------------------
// TODO: Add file number and overall size limit support
var $fileUploadContainer = $('#fileUploadContainer') var $fileUploadContainer = $('#fileUploadContainer')
@ -320,6 +322,12 @@ $fileUploadContainer.on('click', '.existing-file-remove-btn', function($evt) {
$('input[type="checkbox"].answer-portfolio-select').on('change', function($evt) { $('input[type="checkbox"].answer-portfolio-select').on('change', function($evt) {
var $that = $(this) var $that = $(this)
if ($('input[type="checkbox"].answer-portfolio-select:checked').length > 4) {
$that.prop('checked', false)
return
}
var portfId = $that.val() var portfId = $that.val()
var $container = $('#answer-portfolio-photo-widget-container') var $container = $('#answer-portfolio-photo-widget-container')

@ -8,7 +8,7 @@ import itertools
from .models import Project, ProjectFile, Portfolio, Answer, AnswerMessage, Realty, PortfolioPhoto, Stage from .models import Project, ProjectFile, Portfolio, Answer, AnswerMessage, Realty, PortfolioPhoto, Stage
from common.models import Location from common.models import Location
from specializations.models import Specialization from specializations.models import Specialization
from users.models import User from users.models import User, Team
class ProjectFilterForm(forms.ModelForm): class ProjectFilterForm(forms.ModelForm):
@ -184,21 +184,37 @@ class ProjectAnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request') self.request = kwargs.pop('request')
answer_as_team = kwargs.pop('answer_as_team', None) # Optional kwarg self.project = kwargs.pop('project')
self.answer_as_team = kwargs.pop('answer_as_team', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if answer_as_team: if self.answer_as_team:
self.fields['secure_deal_only'].label = 'Работаем только по безопасной сделке' self.fields['secure_deal_only'].label = 'Работаем только по безопасной сделке'
members = self.request.user.team.contractors.all() try: members = self.request.user.team.contractors.all()
except Team.DoesNotExist: members = ()
# portfolios = tuple(itertools.chain(*tuple(contr.portfolios.all() for contr in members)))
portfolios = Portfolio.objects.filter(user__pk__in=tuple(m.pk for m in members)) portfolios = Portfolio.objects.filter(user__pk__in=tuple(m.pk for m in members))
self.fields['portfolios'].queryset = portfolios self.fields['portfolios'].queryset = portfolios
else: else:
self.fields['secure_deal_only'].label = 'Работаю только по безопасной сделке' self.fields['secure_deal_only'].label = 'Работаю только по безопасной сделке'
self.fields['portfolios'].queryset = self.request.user.portfolios.all() self.fields['portfolios'].queryset = self.request.user.portfolios.all()
if self.project and self.project.price_and_term_required:
self.fields['budget'].required = True
self.fields['currency'].required = True
self.fields['term'].required = True
self.fields['term_type'].required = True
def clean(self):
if self.answer_as_team:
try:
self.request.user.team
except Team.DoesNotExist:
raise forms.ValidationError('У исполнителя отсутствует команда')
return super().clean()
class ProjectAnswerMessageForm(forms.ModelForm): class ProjectAnswerMessageForm(forms.ModelForm):

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-16 11:39
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('projects', '0008_auto_20160815_1900'),
]
operations = [
migrations.AlterField(
model_name='answermessage',
name='content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
),
migrations.AlterField(
model_name='answermessage',
name='object_id',
field=models.IntegerField(blank=True, null=True),
),
]

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-16 16:42
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('projects', '0009_auto_20160816_1439'),
]
operations = [
migrations.AlterField(
model_name='answer',
name='budget',
field=models.DecimalField(blank=True, decimal_places=0, max_digits=10, null=True),
),
migrations.AlterField(
model_name='answer',
name='currency',
field=models.CharField(blank=True, choices=[('rur', 'RUR'), ('usd', 'USD'), ('eur', 'EUR')], max_length=5, null=True),
),
migrations.AlterField(
model_name='answer',
name='term',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='answer',
name='term_type',
field=models.CharField(blank=True, choices=[('project', 'За проект'), ('hour', 'За час'), ('day', 'За день'), ('month', 'За месяц')], max_length=10, null=True),
),
]

@ -118,14 +118,14 @@ class ProjectFile(models.Model):
class Answer(models.Model): class Answer(models.Model):
budget = models.DecimalField(max_digits=10, decimal_places=0) budget = models.DecimalField(max_digits=10, decimal_places=0, blank=True, null=True)
created = models.DateTimeField(default=timezone.now) created = models.DateTimeField(default=timezone.now)
currency = models.CharField(max_length=5, choices=CURRENCIES, default='rur') currency = models.CharField(max_length=5, choices=CURRENCIES, blank=True, null=True)
portfolios = models.ManyToManyField('Portfolio', related_name ='answers', blank=True) portfolios = models.ManyToManyField('Portfolio', related_name ='answers', blank=True)
project = models.ForeignKey(Project, related_name='answers') project = models.ForeignKey(Project, related_name='answers')
secure_deal_only = models.BooleanField(default=False) secure_deal_only = models.BooleanField(default=False)
term = models.IntegerField(default=0) term = models.IntegerField(blank=True, null=True)
term_type = models.CharField(max_length=10, choices=TERMS, default='hour') term_type = models.CharField(max_length=10, choices=TERMS, blank=True, null=True)
content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team')) content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team'))
object_id = models.IntegerField() object_id = models.IntegerField()
@ -146,8 +146,14 @@ class AnswerMessage(models.Model):
is_sender_customer = models.BooleanField(default=False) is_sender_customer = models.BooleanField(default=False)
text = models.TextField() text = models.TextField()
content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team')) content_type = models.ForeignKey(
object_id = models.IntegerField() ContentType,
limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team'),
null=True,
blank=True,
)
object_id = models.IntegerField(null=True, blank=True)
contractor_or_team = GenericForeignKey('content_type', 'object_id') contractor_or_team = GenericForeignKey('content_type', 'object_id')
def __str__(self): def __str__(self):

@ -119,25 +119,28 @@
</div> </div>
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
{% if request.user.is_contractor %} {% if request.user.is_contractor and not answer %}
<a href="#" class="new-answer"> <a href="#" onclick="$('.-project-answer-form').toggle('slow'); return false" class="new-answer">
Ответить на проект Ответить на проект
</a> </a>
<div class="polsF1 pols-new disTab">
<div class="col-lg-3"> {% if can_answer_as_team %}
<form action="{% url 'projects:detail' pk=project.pk %}" method="GET" novalidate> <div class="polsF1 pols-new disTab">
<select name="answer_as_team" class="selectpicker" onchange="$(this).closest('form').submit()"> <div class="col-lg-3">
{% if answer_as_team %} <form action="{% url 'projects:detail' pk=project.pk %}" method="GET" novalidate>
<option value="">От своего имени</option> <select name="answer_as_team" class="selectpicker" onchange="$(this).closest('form').submit()">
<option value="on" selected>От имени группы</option> {% if answer_as_team %}
{% else %} <option value="">От своего имени</option>
<option value="" selected>От своего имени</option> <option value="on" selected>От имени группы</option>
<option value="on">От имени группы</option> {% else %}
{% endif %} <option value="" selected>От своего имени</option>
</select> <option value="on">От имени группы</option>
</form> {% endif %}
</select>
</form>
</div>
</div> </div>
</div> {% endif %}
{% elif request.user.is_customer %} {% elif request.user.is_customer %}
<a href="{% url 'projects:customer-project-edit' pk=project.pk %}?back={{ request.path }}" class="linkProIn linkProIn1"> <a href="{% url 'projects:customer-project-edit' pk=project.pk %}?back={{ request.path }}" class="linkProIn linkProIn1">
Редактировать Редактировать
@ -303,7 +306,7 @@
{# </div>#} {# </div>#}
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
<form action="{% url 'projects:create-answer-message' pk=project.pk %}" method="POST" novalidate> <form action="{% url 'projects:create-answer-message' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}"> <input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">
@ -316,11 +319,17 @@
</div> </div>
</div> </div>
{% else %} {% else %}
<form action="{% url 'projects:detail' pk=project.pk %}" method="POST" enctype="multipart/form-data" novalidate> <form action="{% url 'projects:detail' pk=project.pk %}" method="POST" enctype="multipart/form-data" novalidate class="-project-answer-form" s--tyle="display: none">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}"> <input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">
<input type="hidden" name="answer_as_team" value="{% if answer_as_team %}on{% endif %}"> <input type="hidden" name="answer_as_team" value="{% if answer_as_team %}on{% endif %}">
<div class="polsF1 disTab">
<div class="col-xs-12" style="color: red">
{{ form.non_field_errors|safe }}
</div>
</div>
<div class="polsF1 disTab"> <div class="polsF1 disTab">
<div class="col-lg-3"> <div class="col-lg-3">
<div class="textAreaBlock2 text-nn box-sizing disTab"> <div class="textAreaBlock2 text-nn box-sizing disTab">
@ -331,6 +340,7 @@
<div class="col-lg-3"> <div class="col-lg-3">
<div class="textAreaBlock2 text-nn box-sizing disTab"> <div class="textAreaBlock2 text-nn box-sizing disTab">
<p><span style="color: red">{{ form.currency.errors.as_text }}</span></p>
{{ form.currency }} {{ form.currency }}
</div> </div>
</div> </div>
@ -344,6 +354,7 @@
<div class="col-lg-3"> <div class="col-lg-3">
<div class="textAreaBlock2 text-nn box-sizing disTab"> <div class="textAreaBlock2 text-nn box-sizing disTab">
<p><span style="color: red">{{ form.term_type.errors.as_text }}</span></p>
{{ form.term_type }} {{ form.term_type }}
</div> </div>
</div> </div>
@ -534,7 +545,7 @@
</div> </div>
<div class="col-lg-3 retts"> <div class="col-lg-3 retts">
<a href="javascript:void(0)" class="candLink candLink1"> <a href="{% url 'projects:add-candidate' answer_id=answer.pk project_id=project.pk %}" class="candLink candLink1">
Кандидат Кандидат
</a> </a>
<a href="{% url 'chat:chat-user' %}" class="candLink candLink2"> <a href="{% url 'chat:chat-user' %}" class="candLink candLink2">
@ -594,7 +605,7 @@
{# </div>#} {# </div>#}
<div class="col-lg-10 col-lg-offset-1"> <div class="col-lg-10 col-lg-offset-1">
<form action="{% url 'projects:create-answer-message' pk=project.pk %}" method="POST" novalidate> <form action="{% url 'projects:create-answer-message' pk=answer.pk %}" method="POST" novalidate>
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}"> <input type="hidden" name="next" value="{% url 'projects:detail' pk=project.pk %}">

@ -32,7 +32,7 @@ urlpatterns = [
urls.url(r'^(?P<pk>\d+)/restore/$', CustomerProjectRestoreView.as_view(), name='customer-project-restore'), 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+)/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'^create-answer-message/(?P<pk>\d+)/$', ProjectAnswerCreateMessageView.as_view(), name='create-answer-message'),
urls.url(r'^portfolio/create/$', contractor_portfolio_create, name='contractor-portfolio-create'), 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'), urls.url(r'^portfolio/(?P<pk>\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'),

@ -1,9 +1,9 @@
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.core.files.base import ContentFile
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
from django.core.files.base import ContentFile
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseForbidden, HttpResponseRedirect, HttpResponse, Http404 from django.http import HttpResponseForbidden, HttpResponseRedirect, HttpResponse, Http404
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
@ -11,10 +11,7 @@ from django.views.generic import ListView, DetailView, CreateView, DeleteView, V
from django.views.generic.base import ContextMixin from django.views.generic.base import ContextMixin
from pprint import pprint, pformat from pprint import pprint, pformat
import json import json
import pydash as _; import pydash as _; _.map = _.map_; _.filter = _.filter_
_.map = _.map_;
_.filter = _.filter_
import re import re
from .mixins import LastAccessMixin from .mixins import LastAccessMixin
@ -50,35 +47,28 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
if request.user.is_authenticated() and request.user.is_contractor(): if request.user.is_authenticated() and request.user.is_contractor():
project_answers = project.answers.all() project_answers = project.answers.all()
got_answer = None
contractor = request.user contractor = request.user
answer = contractor.contractor_answers.first()
if answer and answer in project_answers: answer = _.first(_.filter(project_answers, lambda a: a.author == contractor))
got_answer = True
else: if not answer:
team = None try: team = contractor.team
answer = None except Team.DoesNotExist: team = None
try:
team = contractor.team
except Team.DoesNotExist:
pass
if team: if team:
answer = team.answers.first() answer = _.first(_.filter(project_answers, lambda a: a.author == team))
if answer and answer in project_answers:
got_answer = True
if got_answer: context.update({'answer': answer})
context.update({'answer': answer})
else: if not answer:
if request.GET.get('answer_as_team') == 'on': # TODO: Check for actual possibility to answer as a team 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}) context.update({'answer_as_team': True})
form = self.form_class(request=request, answer_as_team=True) form = self.form_class(request=request, answer_as_team=True, project=project)
else: else:
form = self.form_class(request=request) form = self.form_class(request=request, project=project)
context.update({'form': form}) context.update({'form': form})
@ -88,14 +78,15 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
if request.user.is_authenticated() and request.user.is_contractor(): if request.user.is_authenticated() and request.user.is_contractor():
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
answer_as_team = None 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': # TODO: Check for actual possibility to answer as a team if request.POST.get('answer_as_team') == 'on':
answer_as_team = True answer_as_team = True
if answer_as_team: if answer_as_team:
form = self.form_class(request.POST, request=request, answer_as_team=True) form = self.form_class(request.POST, request=request, answer_as_team=True, project=project)
else: else:
form = self.form_class(request.POST, request=request) form = self.form_class(request.POST, request=request, project=project)
project = get_object_or_404(Project, pk=kwargs.get('pk')) project = get_object_or_404(Project, pk=kwargs.get('pk'))
context.update({'project': project}) context.update({'project': project})
@ -111,10 +102,10 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
if len(file.name) <= 255: if len(file.name) <= 255:
AnswerFile.objects.create(file=file, name=file.name, answer=answer) AnswerFile.objects.create(file=file, name=file.name, answer=answer)
answer_message = AnswerMessage(text=form.cleaned_data.get('text'), is_sender_customer=False) message = AnswerMessage(text=form.cleaned_data.get('text'), is_sender_customer=False)
answer_message.answer = answer message.answer = answer
answer_message.contractor_or_team = answer.author message.contractor_or_team = answer.author
answer_message.save() message.save()
messages.info(request, 'Отклик успешно размещён') messages.info(request, 'Отклик успешно размещён')
redirect_to = request.POST.get('next') redirect_to = request.POST.get('next')
@ -145,12 +136,22 @@ class ProjectAnswerCreateMessageView(BaseMixin, View):
form = self.form_class(request.POST, request=request) form = self.form_class(request.POST, request=request)
if form.is_valid(): if form.is_valid():
# project = form.save(commit=False) message = form.save(commit=False)
# project.customer = request.user
# project.save() if request.user.is_contractor():
# form.save_m2m() answer = get_object_or_404(request.user.contractor_answers, pk=kwargs.get('pk'))
message.answer = answer
message.is_sender_customer = False
message.contractor_or_team = request.user
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, 'OK') messages.info(request, 'Сообщение успешно размещено')
else: else:
if form.errors: if form.errors:
messages.info(request, ( messages.info(request, (

@ -37,7 +37,7 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div style="position: absolute; left: 0; top: 0; padding: 6px; color: black; background-color: #BADA55; z-index: 50"> <div style="position: absolute; left: 0; top: 0; padding: 6px; color: black; background-color: {% if request.user.is_contractor %}#BADA55{% else %}#C0FFEE{% endif %}; z-index: 50">
{{ request.user }}<br> {{ request.user }}<br>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}

Loading…
Cancel
Save