remotes/origin/PR-39
Mukhtar 10 years ago
commit 4dd71cff71
  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 ---------------------------------------
// TODO: Add file number and overall size limit support
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) {
var $that = $(this)
if ($('input[type="checkbox"].answer-portfolio-select:checked').length > 4) {
$that.prop('checked', false)
return
}
var portfId = $that.val()
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 common.models import Location
from specializations.models import Specialization
from users.models import User
from users.models import User, Team
class ProjectFilterForm(forms.ModelForm):
@ -184,21 +184,37 @@ class ProjectAnswerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
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)
if answer_as_team:
if self.answer_as_team:
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))
self.fields['portfolios'].queryset = portfolios
else:
self.fields['secure_deal_only'].label = 'Работаю только по безопасной сделке'
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):

@ -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):
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)
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)
project = models.ForeignKey(Project, related_name='answers')
secure_deal_only = models.BooleanField(default=False)
term = models.IntegerField(default=0)
term_type = models.CharField(max_length=10, choices=TERMS, default='hour')
term = models.IntegerField(blank=True, null=True)
term_type = models.CharField(max_length=10, choices=TERMS, blank=True, null=True)
is_archive = models.BooleanField(default=False)
content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team'))
@ -147,8 +147,14 @@ class AnswerMessage(models.Model):
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()
content_type = models.ForeignKey(
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')
def __str__(self):

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

@ -34,7 +34,7 @@ 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'^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/(?P<pk>\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'),

@ -1,9 +1,9 @@
from django.conf import settings
from django.contrib import messages
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.urlresolvers import reverse, reverse_lazy
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
@ -11,10 +11,7 @@ from django.views.generic import ListView, DetailView, CreateView, DeleteView, V
from django.views.generic.base import ContextMixin
from pprint import pprint, pformat
import json
import pydash as _;
_.map = _.map_;
_.filter = _.filter_
import pydash as _; _.map = _.map_; _.filter = _.filter_
import re
from .mixins import LastAccessMixin
@ -50,35 +47,28 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
if request.user.is_authenticated() and request.user.is_contractor():
project_answers = project.answers.all()
got_answer = None
contractor = request.user
answer = contractor.contractor_answers.first()
if answer and answer in project_answers:
got_answer = True
else:
team = None
answer = None
try:
team = contractor.team
except Team.DoesNotExist:
pass
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 = team.answers.first()
if answer and answer in project_answers:
got_answer = True
answer = _.first(_.filter(project_answers, lambda a: a.author == team))
if got_answer:
context.update({'answer': answer})
else:
if request.GET.get('answer_as_team') == 'on': # TODO: Check for actual possibility to answer as a 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)
form = self.form_class(request=request, answer_as_team=True, project=project)
else:
form = self.form_class(request=request)
form = self.form_class(request=request, project=project)
context.update({'form': form})
@ -88,14 +78,15 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
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': # TODO: Check for actual possibility to answer as a team
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)
form = self.form_class(request.POST, request=request, answer_as_team=True, project=project)
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'))
context.update({'project': project})
@ -111,10 +102,10 @@ class ProjectDetailWithAnswerView(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()
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')
@ -145,12 +136,22 @@ class ProjectAnswerCreateMessageView(BaseMixin, View):
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()
message = form.save(commit=False)
if request.user.is_contractor():
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:
if form.errors:
messages.info(request, (

@ -37,7 +37,7 @@
{% endfor %}
{% 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>
{% if request.user.is_authenticated %}

Loading…
Cancel
Save