diff --git a/api/views.py b/api/views.py index 7fa861a..f208d15 100755 --- a/api/views.py +++ b/api/views.py @@ -13,7 +13,7 @@ from projects.serializers import ( from projects.filters import ( ProjectFilterSet, RealtyFilterSet, StageFilterSet, PortfolioFilterSet, OrderFilterSet, - PortfolioPhotoFilterSet, + PortfolioPhotoFilterSet, AnswerFilterSet, ) from specializations.models import Specialization @@ -22,7 +22,7 @@ from specializations.filters import SpecializationFilterSet from users.models import User, ContractorResumeFiles, ContractorResume, Team from users.serializers import UserSerializer, ContractorResumeFilesSerializer, ContractorResumeSerializer, TeamSerializer -from users.filters import UserFilterSet, ContractorResumeFilesFilterSet, ContractorResumeFilterSet +from users.filters import UserFilterSet, TeamFilterSet, ContractorResumeFilesFilterSet, ContractorResumeFilterSet from common.models import Location from common.serializers import LocationSerializer @@ -93,6 +93,16 @@ class ProjectViewSet(ModelViewSet): filter_class = ProjectFilterSet # permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + def get_queryset(self): + qs = super().get_queryset() + + x_no_contractor_answer = self.request.GET.get('x_no_contractor_answer', '') + + if x_no_contractor_answer.isdecimal(): + qs = qs.exclude(answers__object_id=x_no_contractor_answer, answers__content_type__model='user') + + return qs + class NoteViewSet(ModelViewSet): queryset = Notes.objects.all() @@ -221,10 +231,10 @@ class WorkSellPhotoViewSet(ModelViewSet): class AnswerViewSet(ModelViewSet): queryset = Answer.objects.all() serializer_class = AnswerSerializer - # filter_class = AnswerFilterSet + filter_class = AnswerFilterSet class TeamViewSet(ModelViewSet): queryset = Team.objects.all() serializer_class = TeamSerializer - # filter_class = TeamFilterSet + filter_class = TeamFilterSet diff --git a/archilance/settings/base.py b/archilance/settings/base.py index 42c8493..86b3daf 100644 --- a/archilance/settings/base.py +++ b/archilance/settings/base.py @@ -266,7 +266,7 @@ if DEBUG: EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' PAGE_SIZE = 10 # Non-api page size (regular views) -API_PAGE_SIZE = 1000 # Django REST framework +API_PAGE_SIZE = 100 # Django REST framework REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ diff --git a/assets/index.js b/assets/index.js index e24700b..8d2e35f 100644 --- a/assets/index.js +++ b/assets/index.js @@ -257,30 +257,42 @@ function initContractorSelect($select, excludeIds) { // Order offer project select --------------------------------------------- - -var projectSelectOptions = { - language: 'ru', - placeholder: 'Выберите проект', // Required by `allowClear` - allowClear: true, -} - -function initProjectSelect($select, customerId) { - return $.ajax({url: '/api/projects/?state=active&customer=' + customerId, method: 'GET', dataType: 'json'}) - .then(function(res) { +;(function() { + var projectSelectOptions = { + language: 'ru', + placeholder: 'Выберите проект', // Required by `allowClear` + allowClear: true, + } + + function initProjectSelect($select, customerId, contractorId) { + var urlObj = new URI('/api/projects/') + + urlObj.setQuery({ + state: 'active', + customer: customerId, + order__contractor__isnull: true, + order__team__isnull: true, + x_no_contractor_answer: contractorId, + }) + + return $.get(urlObj.href()).then(function(res) { var projects = res.results $select.select2(_.merge(projectSelectOptions, { data: _.map(function(project) { return { id: project.id, - text: project.name, + //text: project.name, + text: format('%s (%s)', project.name, project.id), // Tmp origItem: project, } }, projects), })) }) -} - + } + + window.initProjectSelect = initProjectSelect +}()) diff --git a/assets/js/chat.js b/assets/js/chat.js index b4f47b1..ad3b264 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -31,7 +31,7 @@ window.confirm = function (message, callback, caption) { var SocketHandler = function () { domain = domain.replace(':' + port, ''); - var url = 'ws://' + domain + '/chat/' + userId + '/'; + var url = 'ws://' + domain + ':8888/chat/' + userId + '/'; var sock = new WebSocket(url); var intervalId; sock.onopen = function () { diff --git a/chat/filters.py b/chat/filters.py index 6e878f7..0b3da56 100644 --- a/chat/filters.py +++ b/chat/filters.py @@ -5,6 +5,7 @@ from .models import Message, Notes, Documents # # class DocumentsFilterSet(FilterSet): # file = AllLookupsFilter() +# id = AllLookupsFilter() # # sender = RelatedFilter('users.filters.UserFilterSet') # # recipent = RelatedFilter('users.filters.UserFilterSet') # # order = RelatedFilter('projects.filters.OrderFilterSet') @@ -15,13 +16,15 @@ from .models import Message, Notes, Documents class MessageFilterSet(FilterSet): - text = AllLookupsFilter() created = AllLookupsFilter() - sender = RelatedFilter('users.filters.UserFilterSet') - recipent = RelatedFilter('users.filters.UserFilterSet') + id = AllLookupsFilter() private_type = AllLookupsFilter() - team = RelatedFilter('users.filters.TeamFilterSet') + text = AllLookupsFilter() + order = RelatedFilter('projects.filters.OrderFilterSet') + recipent = RelatedFilter('users.filters.UserFilterSet') + sender = RelatedFilter('users.filters.UserFilterSet') + team = RelatedFilter('users.filters.TeamFilterSet') class Meta: model = Message @@ -39,10 +42,12 @@ class DocumentFilterSet(FilterSet): class NoteFilterSet(FilterSet): - text = AllLookupsFilter() created = AllLookupsFilter() - sender = RelatedFilter('users.filters.UserFilterSet') + id = AllLookupsFilter() + text = AllLookupsFilter() + recipent = RelatedFilter('users.filters.UserFilterSet') + sender = RelatedFilter('users.filters.UserFilterSet') class Meta: model = Notes diff --git a/common/filters.py b/common/filters.py index fbfd842..17689f8 100644 --- a/common/filters.py +++ b/common/filters.py @@ -1,17 +1,29 @@ +from django.contrib.contenttypes.models import ContentType from rest_framework_filters import FilterSet, AllLookupsFilter, RelatedFilter + from .models import Location class LocationFilterSet(FilterSet): - children = RelatedFilter('common.filters.LocationFilterSet') id = AllLookupsFilter() level = AllLookupsFilter() lft = AllLookupsFilter() name = AllLookupsFilter() - parent = RelatedFilter('common.filters.LocationFilterSet') rght = AllLookupsFilter() tree_id = AllLookupsFilter() type = AllLookupsFilter() + + children = RelatedFilter('common.filters.LocationFilterSet') + parent = RelatedFilter('common.filters.LocationFilterSet') class Meta: model = Location + + +class ContentTypeFilterSet(FilterSet): + app_label = AllLookupsFilter() + id = AllLookupsFilter() + model = AllLookupsFilter() + + class Meta: + model = ContentType diff --git a/common/serializers.py b/common/serializers.py index cc37b8c..9c455e4 100644 --- a/common/serializers.py +++ b/common/serializers.py @@ -1,3 +1,4 @@ +from django.contrib.contenttypes.models import ContentType from rest_framework.serializers import ModelSerializer from .models import Location @@ -8,15 +9,15 @@ class NestedLocationSerializer(ModelSerializer): model = Location fields = ( + 'children', 'id', + 'level', + 'lft', 'name', - 'children', 'parent', - 'type', - 'lft', 'rght', - 'level', 'tree_id', + 'type', ) @@ -28,13 +29,24 @@ class LocationSerializer(ModelSerializer): model = Location fields = ( + 'children', 'id', + 'level', + 'lft', 'name', - 'children', 'parent', - 'type', - 'lft', 'rght', - 'level', 'tree_id', + 'type', + ) + + +class ContentTypeSerializer(ModelSerializer): + class Meta: + model = ContentType + + fields = ( + 'app_label', + 'id', + 'model', ) diff --git a/projects/filters.py b/projects/filters.py index 5f20c98..b111a72 100755 --- a/projects/filters.py +++ b/projects/filters.py @@ -1,6 +1,16 @@ from rest_framework_filters import FilterSet, RelatedFilter, AllLookupsFilter -from .models import Project, Stage, Order, Realty, BuildingClassfication, ConstructionType, Portfolio, PortfolioPhoto +from .models import ( + Answer, + BuildingClassfication, + ConstructionType, + Order, + Portfolio, + PortfolioPhoto, + Project, + Realty, + Stage, +) class BuildingClassficationFilterSet(FilterSet): @@ -26,6 +36,7 @@ class ProjectFilterSet(FilterSet): cro = AllLookupsFilter() currency = AllLookupsFilter() deal_type = AllLookupsFilter() + id = AllLookupsFilter() name = AllLookupsFilter() price_and_term_required = AllLookupsFilter() state = AllLookupsFilter() @@ -34,7 +45,9 @@ class ProjectFilterSet(FilterSet): text = AllLookupsFilter() work_type = AllLookupsFilter() + answers = RelatedFilter('projects.filters.AnswerFilterSet') customer = RelatedFilter('users.filters.UserFilterSet') + order = RelatedFilter('projects.filters.OrderFilterSet') realty = RelatedFilter('projects.filters.RealtyFilterSet') specialization = RelatedFilter('specializations.filters.SpecializationFilterSet') @@ -42,31 +55,58 @@ class ProjectFilterSet(FilterSet): model = Project -class OrderFilterSet(FilterSet): - id = AllLookupsFilter() - contractor = RelatedFilter('users.filters.UserFilterSet') - team = RelatedFilter('users.filters.TeamFilterSet') +class AnswerFilterSet(FilterSet): + budget = AllLookupsFilter() created = AllLookupsFilter() + currency = AllLookupsFilter() + id = AllLookupsFilter() + is_archive = AllLookupsFilter() + object_id = AllLookupsFilter() + rejected = AllLookupsFilter() + secure_deal_only = AllLookupsFilter() + term = AllLookupsFilter() + term_type = AllLookupsFilter() + + # author = ... # ??? + + # messages = RelatedFilter('...') + content_type = RelatedFilter('common.filters.ContentTypeFilterSet') + contractors = RelatedFilter('users.filters.UserFilterSet') + portfolios = RelatedFilter('projects.filters.PortfolioFilterSet') project = RelatedFilter('projects.filters.ProjectFilterSet') + teams = RelatedFilter('users.filters.TeamFilterSet') + + class Meta: + model = Answer + + +class OrderFilterSet(FilterSet): + created = AllLookupsFilter() + id = AllLookupsFilter() secure = AllLookupsFilter() status = AllLookupsFilter() + + contractor = RelatedFilter('users.filters.UserFilterSet') + project = RelatedFilter('projects.filters.ProjectFilterSet') + team = RelatedFilter('users.filters.TeamFilterSet') class Meta: model = Order class StageFilterSet(FilterSet): + cost = AllLookupsFilter() + cost_type = AllLookupsFilter() id = AllLookupsFilter() + is_paid = AllLookupsFilter() name = AllLookupsFilter() + pos = AllLookupsFilter() result = AllLookupsFilter() - cost = AllLookupsFilter() + status = AllLookupsFilter() term = AllLookupsFilter() - cost_type = AllLookupsFilter() term_type = AllLookupsFilter() - status = AllLookupsFilter() - pos = AllLookupsFilter() + order = RelatedFilter('projects.filters.OrderFilterSet') - is_paid = AllLookupsFilter() class Meta: model = Stage @@ -87,6 +127,7 @@ class RealtyFilterSet(FilterSet): class PortfolioPhotoFilterSet(FilterSet): id = AllLookupsFilter() + portfolio = RelatedFilter('projects.filters.PortfolioFilterSet') # img = ??? diff --git a/projects/forms.py b/projects/forms.py index 0052f6b..6196118 100644 --- a/projects/forms.py +++ b/projects/forms.py @@ -207,20 +207,23 @@ class PortfolioEditForm(forms.ModelForm): fields = ( 'building_classification', 'construction_type', + 'specialization', + + 'budget', 'currency', + 'description', 'name', - 'photos', + 'term', 'term_type', + 'worksell', ) - # fields = '__all__' - # - # widgets = { - # 'construction_type': forms.Select(attrs={'class': 'selectpicker'}), - # 'building_classification': forms.Select(attrs={'class': 'selectpicker'}), - # 'currency': forms.Select(attrs={'class': 'selectpicker'}), - # 'term_type': forms.Select(attrs={'class': 'selectpicker'}), - # } + widgets = { + 'construction_type': forms.Select(attrs={'class': 'selectpicker'}), + 'building_classification': forms.Select(attrs={'class': 'selectpicker'}), + 'currency': forms.Select(attrs={'class': 'selectpicker'}), + 'term_type': forms.Select(attrs={'class': 'selectpicker'}), + } def __init__(self, *args, **kwargs): # self.request = kwargs.pop('request') diff --git a/projects/migrations/0031_merge.py b/projects/migrations/0031_merge.py new file mode 100644 index 0000000..bc7937d --- /dev/null +++ b/projects/migrations/0031_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-14 13:00 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0030_auto_20160912_1912'), + ('projects', '0030_auto_20160912_1305'), + ] + + operations = [ + ] diff --git a/projects/models.py b/projects/models.py index a13cc51..b792b96 100644 --- a/projects/models.py +++ b/projects/models.py @@ -119,7 +119,7 @@ class Project(models.Model, HitCountMixin): class ProjectFile(models.Model): - file = models.FileField(upload_to='projects/project_files') + file = models.FileField(upload_to='projects/project_files/') project = models.ForeignKey(Project, related_name='files', blank=True, null=True) class Meta: @@ -189,7 +189,7 @@ class AnswerMessage(models.Model): class AnswerFile(models.Model): answer = models.ForeignKey(Answer, related_name='files', blank=True, null=True) name = models.CharField(max_length=255) - file = models.FileField(upload_to='projects/answer_files') + file = models.FileField(upload_to='projects/answer_files/') class Meta: verbose_name = 'Файл для отклика' @@ -344,7 +344,7 @@ class Portfolio(models.Model): class PortfolioPhoto(models.Model): - img = models.ImageField(upload_to='projects/portfolio') + img = models.ImageField(upload_to='projects/portfolio/') portfolio = models.ForeignKey(Portfolio, related_name='photos') class Meta: diff --git a/projects/serializers.py b/projects/serializers.py index 07658ad..e17b96d 100755 --- a/projects/serializers.py +++ b/projects/serializers.py @@ -3,11 +3,40 @@ from generic_relations.relations import GenericRelatedField from rest_framework.serializers import ModelSerializer, ImageField, FileField, SerializerMethodField, PrimaryKeyRelatedField, ReadOnlyField from .models import Project, Realty, BuildingClassfication, ConstructionType, Order, Stage, Portfolio, PortfolioPhoto, Answer, AnswerFile -from common.serializers import LocationSerializer +from common.serializers import LocationSerializer, ContentTypeSerializer from specializations.serializers import SpecializationSerializer from users.models import User, Team from users.serializers import UserSerializer, TeamSerializer + +class AnswerFileSerializer(ModelSerializer): + file = FileField() + + class Meta: + model = AnswerFile + + fields = ( + 'file', + 'id', + 'name', + # 'answer', + ) + + +class PortfolioPhotoSerializer(ModelSerializer): + img = ImageField() + portfolio_id = PrimaryKeyRelatedField(read_only=True, source='portfolio') + + class Meta: + model = PortfolioPhoto + + fields = ( + 'id', + 'img', + 'portfolio_id', + ) + + class BuildingClassficationSerializer(ModelSerializer): class Meta: model = BuildingClassfication @@ -93,16 +122,53 @@ class OrderSerializer_(ModelSerializer): ) +class AnswerSerializer_(ModelSerializer): + project_id = PrimaryKeyRelatedField(read_only=True, source='project') + portfolio_ids = PrimaryKeyRelatedField(read_only=True, source='portfolios', many=True) + + content_type = ContentTypeSerializer() + files = AnswerFileSerializer(many=True) + + author = GenericRelatedField({ + User: UserSerializer(), + Team: TeamSerializer() + }) + + class Meta: + model = Answer + + fields = ( + 'budget', + 'created', + 'currency', + 'id', + 'is_archive', + 'object_id', + 'portfolio_ids', + 'project_id', + 'rejected', + 'secure_deal_only', + 'term', + 'term_type', + + 'author', # Generic related field + 'content_type', + 'files', + ) + + class ProjectSerializer(ModelSerializer): + answers = AnswerSerializer_(many=True) customer = UserSerializer() - specialization = SpecializationSerializer() - realty = RealtySerializer() order = OrderSerializer_() # TODO: Can't serialize a reverse/reciprocal relation + realty = RealtySerializer() + specialization = SpecializationSerializer() class Meta: model = Project - + fields = ( + 'answers', 'budget', 'budget_by_agreement', 'created', @@ -158,25 +224,10 @@ class OrderSerializer(ModelSerializer): return False -class PortfolioPhotoSerializer(ModelSerializer): - img = ImageField() - portfolio_id = PrimaryKeyRelatedField(read_only=True, source='portfolio') - - class Meta: - model = PortfolioPhoto - - fields = ( - 'id', - 'img', - 'portfolio_id', - ) - - class PortfolioSerializer(ModelSerializer): # answers = AnswerSerializer(many=True) building_classification = BuildingClassficationSerializer() construction_type = ConstructionTypeSerializer() - id = ReadOnlyField() location = LocationSerializer() photos = PortfolioPhotoSerializer(many=True) specialization = SpecializationSerializer() @@ -204,24 +255,11 @@ class PortfolioSerializer(ModelSerializer): ) -class AnswerFileSerializer(ModelSerializer): - file = FileField() - - class Meta: - model = AnswerFile - - fields = ( - 'file', - 'id', - 'name', - # 'answer', - ) - - class AnswerSerializer(ModelSerializer): + content_type = ContentTypeSerializer() files = AnswerFileSerializer(many=True) portfolios = PortfolioSerializer(many=True) - project = ProjectSerializer + project = ProjectSerializer() author = GenericRelatedField({ User: UserSerializer(), @@ -236,15 +274,18 @@ class AnswerSerializer(ModelSerializer): 'created', 'currency', 'id', + 'is_archive', + 'object_id', + 'rejected', 'secure_deal_only', 'term', 'term_type', - 'text', + 'author', # Generic related field + 'content_type', 'files', 'portfolios', 'project', - 'author', # Generic related field # 'candidates', ) diff --git a/projects/templates/project_detail.html b/projects/templates/project_detail.html index 1d49102..d954d47 100644 --- a/projects/templates/project_detail.html +++ b/projects/templates/project_detail.html @@ -109,7 +109,7 @@
Специализации
Бюджет{{ form.budget.errors.as_text }}
@@ -82,6 +98,47 @@Классификация здания
{{ form.building_classification}}Фотографии
+ + {% for photo in form.photos.field.queryset.all %} + + {% endfor %} + + + + + +