diff --git a/api/test.py b/api/test.py index 269a6c8..7bca6e1 100644 --- a/api/test.py +++ b/api/test.py @@ -3,4 +3,3 @@ from rest_framework.test import APITestCase class FileUploadTests(APITestCase): pass - diff --git a/api/urls.py b/api/urls.py index 79287ed..884a968 100755 --- a/api/urls.py +++ b/api/urls.py @@ -22,7 +22,6 @@ from .views import ( WorkSellViewSet, ) - router = routers.DefaultRouter() router.register(r'answers', AnswerViewSet) diff --git a/api/views.py b/api/views.py index f208d15..de937c8 100755 --- a/api/views.py +++ b/api/views.py @@ -4,41 +4,33 @@ from rest_framework import permissions from rest_framework.pagination import PageNumberPagination from rest_framework.viewsets import ModelViewSet -from projects.models import Project, Realty, Stage, Portfolio, PortfolioPhoto, Answer, AnswerFile, Order - -from projects.serializers import ( - ProjectSerializer, RealtySerializer, StageSerializer, PortfolioSerializer, - PortfolioPhotoSerializer, AnswerSerializer, OrderSerializer, AnswerFileSerializer, -) - +from chat.filters import MessageFilterSet, NoteFilterSet, DocumentFilterSet +from chat.models import Message, Notes, Documents, NewMessage +from chat.serializers import MessageSerializer, NoteSerializer, DocumentsSerializer +from common.filters import LocationFilterSet +from common.models import Location +from common.serializers import LocationSerializer from projects.filters import ( ProjectFilterSet, RealtyFilterSet, StageFilterSet, PortfolioFilterSet, OrderFilterSet, PortfolioPhotoFilterSet, AnswerFilterSet, ) - +from projects.models import Project, Realty, Stage, Portfolio, PortfolioPhoto, Answer, Order +from projects.serializers import ( + ProjectSerializer, RealtySerializer, StageSerializer, PortfolioSerializer, + PortfolioPhotoSerializer, AnswerSerializer, OrderSerializer, ) +from reviews.filters import ReviewFilterSet +from reviews.models import Review +from reviews.serializers import ReviewSerializer +from specializations.filters import SpecializationFilterSet from specializations.models import Specialization from specializations.serializers import SpecializationSerializer -from specializations.filters import SpecializationFilterSet - +from users.filters import UserFilterSet, TeamFilterSet from users.models import User, ContractorResumeFiles, ContractorResume, Team -from users.serializers import UserSerializer, ContractorResumeFilesSerializer, ContractorResumeSerializer, TeamSerializer -from users.filters import UserFilterSet, TeamFilterSet, ContractorResumeFilesFilterSet, ContractorResumeFilterSet - -from common.models import Location -from common.serializers import LocationSerializer -from common.filters import LocationFilterSet - -from chat.models import Message, Notes, Documents, NewMessage -from chat.serializers import MessageSerializer, NoteSerializer, DocumentsSerializer -from chat.filters import MessageFilterSet, NoteFilterSet, DocumentFilterSet - -from reviews.models import Review -from reviews.serializers import ReviewSerializer -from reviews.filters import ReviewFilterSet - +from users.serializers import UserSerializer, ContractorResumeFilesSerializer, ContractorResumeSerializer, \ + TeamSerializer +from work_sell.filters import WorkSellFilterSet, WorkSellPhotoFilterSet from work_sell.models import WorkSell, WorkSellPhoto from work_sell.serializers import WorkSellSerializer, WorkSellPhotoSerializer -from work_sell.filters import WorkSellFilterSet, WorkSellPhotoFilterSet class ContractorResumeFilesViewSet(ModelViewSet): @@ -83,7 +75,9 @@ class DocumentViewSet(ModelViewSet): if search_param: # import code; code.interact(local=dict(globals(), **locals())) if search_param == 'in': - queryset = queryset.filter(Q(sender__in=[sender_id, recipent_id]),Q(recipent__in=[sender_id, recipent_id])).filter(order__isnull=True).order_by('created') + queryset = queryset.filter(Q(sender__in=[sender_id, recipent_id]), + Q(recipent__in=[sender_id, recipent_id])).filter( + order__isnull=True).order_by('created') return queryset @@ -91,16 +85,17 @@ class ProjectViewSet(ModelViewSet): queryset = Project.objects.all() serializer_class = ProjectSerializer 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 @@ -118,7 +113,8 @@ class NoteViewSet(ModelViewSet): if search_param: # import code; code.interact(local=dict(globals(), **locals())) if search_param == 'in': - queryset = queryset.filter(Q(sender__in=[sender_id,recipent_id]),Q(recipent__in=[sender_id,recipent_id])).order_by('created') + queryset = queryset.filter(Q(sender__in=[sender_id, recipent_id]), + Q(recipent__in=[sender_id, recipent_id])).order_by('created') return queryset @@ -135,11 +131,11 @@ class MessageViewSet(ModelViewSet): if search_param: # import code; code.interact(local=dict(globals(), **locals())) if search_param == 'in': - queryset = queryset.filter(Q(sender__in=[sender_id,recipent_id]),Q(recipent__in=[sender_id,recipent_id])).\ - filter(order__isnull=True).\ + queryset = queryset.filter(Q(sender__in=[sender_id, recipent_id]), + Q(recipent__in=[sender_id, recipent_id])). \ + filter(order__isnull=True). \ filter(~Q(sender=F('recipent'))) - queryset = queryset.order_by('created') return queryset @@ -187,8 +183,8 @@ class LocationViewSet(ModelViewSet): class PortfolioPagination(PageNumberPagination): - page_size = settings.API_PAGE_SIZE # Default page size - page_size_query_param = 'page_size' # Provide custom page size through a query param + page_size = settings.API_PAGE_SIZE # Default page size + page_size_query_param = 'page_size' # Provide custom page size through a query param max_page_size = 1000 @@ -205,13 +201,12 @@ class PortfolioPhotoViewSet(ModelViewSet): filter_class = PortfolioPhotoFilterSet - - class WorkSellPagination(PageNumberPagination): - page_size = settings.API_PAGE_SIZE # Default page size - page_size_query_param = 'page_size' # Provide custom page size through a query param + page_size = settings.API_PAGE_SIZE # Default page size + page_size_query_param = 'page_size' # Provide custom page size through a query param max_page_size = 1000 + class WorkSellViewSet(ModelViewSet): queryset = WorkSell.objects.all() serializer_class = WorkSellSerializer @@ -219,15 +214,12 @@ class WorkSellViewSet(ModelViewSet): pagination_class = WorkSellPagination - class WorkSellPhotoViewSet(ModelViewSet): queryset = WorkSellPhoto.objects.all() serializer_class = WorkSellPhotoSerializer filter_class = WorkSellPhotoFilterSet - - class AnswerViewSet(ModelViewSet): queryset = Answer.objects.all() serializer_class = AnswerSerializer diff --git a/archilance/management/commands/generate_build_classifs.py b/archilance/management/commands/generate_build_classifs.py index 8a90d35..7a26582 100644 --- a/archilance/management/commands/generate_build_classifs.py +++ b/archilance/management/commands/generate_build_classifs.py @@ -1,11 +1,9 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from projects.models import BuildingClassfication @@ -14,5 +12,5 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating building classifications...') print('---------------------------------------') - + _.times(lambda i: BuildingClassfication.objects.create(name='Build. classif. %s' % i), 100) diff --git a/archilance/management/commands/generate_constr_types.py b/archilance/management/commands/generate_constr_types.py index f08b229..ff42271 100644 --- a/archilance/management/commands/generate_constr_types.py +++ b/archilance/management/commands/generate_constr_types.py @@ -1,11 +1,9 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from projects.models import ConstructionType @@ -14,5 +12,5 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating construction types...') print('---------------------------------------') - + _.times(lambda i: ConstructionType.objects.create(name='Constr. type %s' % i), 100) diff --git a/archilance/management/commands/generate_locations.py b/archilance/management/commands/generate_locations.py index 48ad1c1..4dfc3af 100644 --- a/archilance/management/commands/generate_locations.py +++ b/archilance/management/commands/generate_locations.py @@ -1,11 +1,9 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from common.models import Location @@ -14,15 +12,14 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating locations...') print('---------------------------------------') - - + _root = Location.objects.create(name='_root', type='_root') - - depths = ('A','B','C','D') - + + depths = ('A', 'B', 'C', 'D') + for d1 in depths: x = Location.objects.create(name='Страна %s' % d1, type='country', parent=_root) for d2 in depths: - y = Location.objects.create(name='Регион %s-%s' % (d1,d2), type='region', parent=x) + y = Location.objects.create(name='Регион %s-%s' % (d1, d2), type='region', parent=x) for d3 in depths: - z = Location.objects.create(name='Город %s-%s-%s' % (d1,d2,d3), type='town', parent=y) + z = Location.objects.create(name='Город %s-%s-%s' % (d1, d2, d3), type='town', parent=y) diff --git a/archilance/management/commands/generate_perm_groups.py b/archilance/management/commands/generate_perm_groups.py index abd9f39..6820d10 100644 --- a/archilance/management/commands/generate_perm_groups.py +++ b/archilance/management/commands/generate_perm_groups.py @@ -1,11 +1,9 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; +from django.contrib.auth.models import Group from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ class Command(BaseCommand): @@ -13,7 +11,6 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating permission groups...') print('---------------------------------------') - - + Group.objects.create(name='Исполнители') Group.objects.create(name='Заказчики') diff --git a/archilance/management/commands/generate_portfolios.py b/archilance/management/commands/generate_portfolios.py index ba13920..033448f 100644 --- a/archilance/management/commands/generate_portfolios.py +++ b/archilance/management/commands/generate_portfolios.py @@ -1,15 +1,14 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random + +_.map = _.map_; +_.filter = _.filter_ from archilance import util from common.models import Location -from projects.models import Portfolio, PortfolioPhoto, CURRENCIES, TERM_TYPES, BuildingClassfication , ConstructionType +from projects.models import Portfolio, CURRENCIES, TERM_TYPES, BuildingClassfication, ConstructionType from specializations.models import Specialization -from users.models import User, Team, GENDERS +from users.models import User class Command(BaseCommand): @@ -17,8 +16,7 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating portfolios...') print('---------------------------------------') - - + # ('photos', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('budget', 'Relation? False', 'Null? True', 'Blank? True', 'Hidden? False'), # ('building_classification', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), @@ -33,8 +31,8 @@ class Command(BaseCommand): # ('term_type', 'Relation? False', 'Null? True', 'Blank? True', 'Hidden? False'), # ('user', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('worksell', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), - - + + def create_portfolio(i): portf = Portfolio( budget=util.random_amount(), @@ -46,24 +44,26 @@ class Command(BaseCommand): term_type=_.sample(TERM_TYPES)[0], worksell=_.sample((True, False)), ) - + portf.save() - + portf.user = User.contractor_objects.order_by('?').first() - + portf.building_classification = BuildingClassfication.objects.order_by('?').first() portf.construction_type = ConstructionType.objects.order_by('?').first() portf.location = Location.objects.root_nodes()[0].get_descendants().order_by('?').first() portf.specialization = Specialization.objects.root_nodes()[0].get_descendants().order_by('?').first() - - pic_names = _.split('a.png b.png c.jpg d.png e.jpg f.png g.png h.jpg i.png j.png k.jpg l.png m.png n.png o.png p.png q.jpg', ' ') - + + pic_names = _.split( + 'a.png b.png c.jpg d.png e.jpg f.png g.png h.jpg i.png j.png k.jpg l.png m.png n.png o.png p.png q.jpg', + ' ') + _.times( lambda i_: portf.photos.create(img='_sample_files/%s' % util.take_one_random(pic_names)), _.random(1, 15), ) - + portf.save() return portf - + _.times(create_portfolio, 1000) diff --git a/archilance/management/commands/generate_projects.py b/archilance/management/commands/generate_projects.py index 18ad4b2..121e488 100644 --- a/archilance/management/commands/generate_projects.py +++ b/archilance/management/commands/generate_projects.py @@ -1,9 +1,8 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random + +_.map = _.map_; +_.filter = _.filter_ from archilance import util from projects.models import Project, Order, CURRENCIES, TERM_TYPES, Specialization, Realty @@ -15,23 +14,22 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating projects...') print('---------------------------------------') - - + # Fields: - - + + # ('customer', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), # ('specialization', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), - + # ('answers', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('candidates', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('files', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('order', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('realty', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('reviews', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), - - - + + + # ('budget', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('created', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('currency', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), @@ -41,16 +39,16 @@ class Command(BaseCommand): # ('term', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('term_type', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('work_type', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), - + # ('budget_by_agreement', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('cro', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('id', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('price_and_term_required', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('text', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), - - - - + + + + def create_project(i): project = Project( budget=util.random_amount(), @@ -67,20 +65,20 @@ class Command(BaseCommand): work_type=_.sample(Project.WORK_TYPES)[0], state='active', ) - + project.specialization = Specialization.objects.root_nodes()[0].get_descendants().order_by('?').first() project.realty = Realty.objects.order_by('?').first() project.customer = User.customer_objects.filter(is_active=True).order_by('?').first() - + project.save() - + Order.objects.create( project=project, contractor=_.sample((None, User.contractor_objects.order_by('?').first())), secure=_.sample((True, False)), status=_.sample(Order.STATUSES)[0], ) - + return project - - _.times(create_project, 1000) + + _.times(create_project, 1000) diff --git a/archilance/management/commands/generate_realties.py b/archilance/management/commands/generate_realties.py index fdbc345..c6d57dc 100644 --- a/archilance/management/commands/generate_realties.py +++ b/archilance/management/commands/generate_realties.py @@ -1,11 +1,9 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from common.models import Location from projects.models import Realty, BuildingClassfication, ConstructionType from users.models import User @@ -16,8 +14,7 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating realties...') print('---------------------------------------') - - + # # Fields: # # @@ -31,17 +28,18 @@ class Command(BaseCommand): # # # ('name', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), - - + + def create_realty(i): realty = Realty(name='Realty %s' % i) - + realty.building_classification = BuildingClassfication.objects.order_by('?').first() realty.construction_type = ConstructionType.objects.order_by('?').first() realty.location = Location.objects.root_nodes()[0].get_descendants().order_by('?').first() - realty.user = User.objects.filter(groups__name='Заказчики', is_active=True, is_superuser=False).order_by('?').first() - + realty.user = User.objects.filter(groups__name='Заказчики', is_active=True, is_superuser=False).order_by( + '?').first() + realty.save() return realty - - _.times(create_realty, 100) + + _.times(create_realty, 100) diff --git a/archilance/management/commands/generate_reviews.py b/archilance/management/commands/generate_reviews.py index 98f8a94..193084a 100644 --- a/archilance/management/commands/generate_reviews.py +++ b/archilance/management/commands/generate_reviews.py @@ -1,14 +1,12 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random + +_.map = _.map_; +_.filter = _.filter_ from archilance import util -from projects.models import Project, Portfolio -from specializations.models import Specialization -from users.models import User, GENDERS, Team +from projects.models import Project +from users.models import User from reviews.models import Review @@ -17,25 +15,25 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating reviews...') print('---------------------------------------') - + def create_review(i): review = Review() - + review.project = Project.objects.order_by('?').first() review.stars = _.random(1, 5) review.text = util.lorem() review.is_secured = _.sample((True, False)) - + review.save() - + if _.sample((True, False)): review.from_contractor = User.contractor_objects.order_by('?').first() review.target_customer = User.customer_objects.order_by('?').first() else: review.from_customer = User.customer_objects.order_by('?').first() review.target_contractor = User.contractor_objects.order_by('?').first() - + review.save() return review - + _.times(create_review, 300) diff --git a/archilance/management/commands/generate_specializations.py b/archilance/management/commands/generate_specializations.py index a288abe..1ff66d4 100644 --- a/archilance/management/commands/generate_specializations.py +++ b/archilance/management/commands/generate_specializations.py @@ -1,8 +1,9 @@ +import pydash as _; from django.core.management import BaseCommand -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from specializations.models import Specialization @@ -11,17 +12,16 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating specializations...') print('---------------------------------------') - - + _root = Specialization.objects.create(name='_root') - - stages = ('A','B','C','D') - + + stages = ('A', 'B', 'C', 'D') + for s1 in stages: x = Specialization.objects.create(name='Специализация %s' % s1, parent=_root) for s2 in stages: - y = Specialization.objects.create(name='Специализация %s-%s' % (s1,s2), parent=x) + y = Specialization.objects.create(name='Специализация %s-%s' % (s1, s2), parent=x) for s3 in stages: - z = Specialization.objects.create(name='Специализация %s-%s-%s' % (s1,s2,s3), parent=y) + z = Specialization.objects.create(name='Специализация %s-%s-%s' % (s1, s2, s3), parent=y) for s4 in stages: - Specialization.objects.create(name='Специализация %s-%s-%s-%s' % (s1,s2,s3,s4), parent=z) + Specialization.objects.create(name='Специализация %s-%s-%s-%s' % (s1, s2, s3, s4), parent=z) diff --git a/archilance/management/commands/generate_superuser.py b/archilance/management/commands/generate_superuser.py index 0941d23..35ed7ce 100644 --- a/archilance/management/commands/generate_superuser.py +++ b/archilance/management/commands/generate_superuser.py @@ -1,11 +1,9 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from users.models import User @@ -14,5 +12,5 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating superusers...') print('---------------------------------------') - + User.objects.create_superuser('admin@example.com', '123456') diff --git a/archilance/management/commands/generate_teams.py b/archilance/management/commands/generate_teams.py index edee3ef..5dce15f 100644 --- a/archilance/management/commands/generate_teams.py +++ b/archilance/management/commands/generate_teams.py @@ -1,14 +1,13 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random + +_.map = _.map_; +_.filter = _.filter_ from archilance import util from projects.models import Portfolio from specializations.models import Specialization -from users.models import User, GENDERS, Team +from users.models import User, Team class Command(BaseCommand): @@ -16,31 +15,31 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating teams...') print('---------------------------------------') - - + # ('name', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), - + # ('portfolios', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('owner', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('users', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), # ('specializations', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), - - + + contractors = list(User.contractor_objects.order_by('?')) - + owners = util.take(contractors, len(contractors) // 2) members = contractors i = 0 - + for owner in owners: team = Team(name='Team %s' % i) team.save() - + team.owner = owner team.contractors = _.sample(members, _.random(1, 5)) - - team.specializations = Specialization.objects.root_nodes()[0].get_descendants().order_by('?')[:_.random(1, 5)] + + team.specializations = Specialization.objects.root_nodes()[0].get_descendants().order_by('?')[ + :_.random(1, 5)] team.portfolios = Portfolio.objects.order_by('?')[:_.random(1, 10)] - + team.save() i += 1 diff --git a/archilance/management/commands/generate_users.py b/archilance/management/commands/generate_users.py index 90ae119..2a47d78 100644 --- a/archilance/management/commands/generate_users.py +++ b/archilance/management/commands/generate_users.py @@ -1,14 +1,13 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; +from django.contrib.auth.models import Group from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random -from archilance import util +_.map = _.map_; +_.filter = _.filter_ + from common.models import Location from specializations.models import Specialization -from users.models import User, GENDERS +from users.models import User class Command(BaseCommand): @@ -16,15 +15,13 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating users...') print('---------------------------------------') - - - + # # Fields: # # ('contractor_specializations', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), # ('groups', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), # ('user_permissions', 'Relation? True', 'Null? False', '(relation)', 'Hidden? False'), - + # ('answers', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('contractor_financial_info', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), # ('location', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), @@ -47,9 +44,9 @@ class Command(BaseCommand): # ('User_groups+', 'Relation? True', 'Null? True', '(relation)', 'Hidden? True'), # ('User_user_permissions+', 'Relation? True', 'Null? True', '(relation)', 'Hidden? True'), # ('work_sell', 'Relation? True', 'Null? True', '(relation)', 'Hidden? False'), - - - + + + # ('contractor_status', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('created', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('data_joined', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), @@ -57,7 +54,7 @@ class Command(BaseCommand): # ('last_time_visit', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('password', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), # ('username', 'Relation? False', 'Null? False', 'Blank? False', 'Hidden? False'), - + # ('avatar', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('cro', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('date_of_birth', 'Relation? False', 'Null? True', 'Blank? True', 'Hidden? False'), @@ -72,12 +69,12 @@ class Command(BaseCommand): # ('phone', 'Relation? False', 'Null? True', 'Blank? True', 'Hidden? False'), # ('skype', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), # ('website', 'Relation? False', 'Null? False', 'Blank? True', 'Hidden? False'), - - - + + + def create_user(i): username = 'user-%s' % i - + return User.objects.create( first_name='Василий', last_name='Пупкин', @@ -88,17 +85,17 @@ class Command(BaseCommand): contractor_status=_.sample(User.STATUSES)[0], cro=_.sample((True, False)), ) - + users = _.times(create_user, 500) - - + contractor_group = Group.objects.get(name='Исполнители') customer_group = Group.objects.get(name='Заказчики') - + for user in users: user.set_password('123') user.groups.add(customer_group if user.pk % 2 == 0 else contractor_group) - user.contractor_specializations = Specialization.objects.root_nodes()[0].get_descendants().order_by('?')[:_.random(1, 5)] + user.contractor_specializations = Specialization.objects.root_nodes()[0].get_descendants().order_by('?')[ + :_.random(1, 5)] user.location = Location.objects.root_nodes()[0].get_descendants().order_by('?').first() - + user.save() diff --git a/archilance/management/commands/generate_work_sells.py b/archilance/management/commands/generate_work_sells.py index 2b565b5..2999bcc 100644 --- a/archilance/management/commands/generate_work_sells.py +++ b/archilance/management/commands/generate_work_sells.py @@ -1,16 +1,15 @@ -from django.contrib.auth.models import Group, Permission -from django.contrib.contenttypes.models import ContentType +import pydash as _; from django.core.management import BaseCommand -from django.utils import timezone -import pydash as _; _.map = _.map_; _.filter = _.filter_ -import random + +_.map = _.map_; +_.filter = _.filter_ from archilance import util from common.models import Location from projects.models import TERM_TYPES, CURRENCIES, BuildingClassfication, ConstructionType from specializations.models import Specialization from users.models import User, Team -from work_sell.models import WorkSell, WorkSellPhoto +from work_sell.models import WorkSell class Command(BaseCommand): @@ -18,7 +17,7 @@ class Command(BaseCommand): print('---------------------------------------') print('Generating work sells...') print('---------------------------------------') - + def create_work_sell(i): ws = WorkSell( budget=util.random_amount(), @@ -29,27 +28,29 @@ class Command(BaseCommand): term=_.random(0, 20), term_type=_.sample(TERM_TYPES)[0], ) - + ws.save() - + if _.sample((True, False)): ws.contractor = User.contractor_objects.order_by('?').first() else: ws.team = Team.objects.order_by('?').first() - + ws.building_classification = BuildingClassfication.objects.order_by('?').first() ws.construction_type = ConstructionType.objects.order_by('?').first() ws.location = Location.objects.root_nodes()[0].get_descendants().order_by('?').first() ws.specialization = Specialization.objects.root_nodes()[0].get_descendants().order_by('?').first() - - pic_names = _.split('a.png b.png c.jpg d.png e.jpg f.png g.png h.jpg i.png j.png k.jpg l.png m.png n.png o.png p.png q.jpg', ' ') - + + pic_names = _.split( + 'a.png b.png c.jpg d.png e.jpg f.png g.png h.jpg i.png j.png k.jpg l.png m.png n.png o.png p.png q.jpg', + ' ') + _.times( lambda i_: ws.photos.create(img='_sample_files/%s' % util.take_one_random(pic_names)), _.random(1, 15), ) - + ws.save() return ws - + _.times(create_work_sell, 1000) diff --git a/archilance/middlewares.py b/archilance/middlewares.py new file mode 100644 index 0000000..b28a339 --- /dev/null +++ b/archilance/middlewares.py @@ -0,0 +1,16 @@ +from django.utils import translation + + +class LocaleMiddleware(object): + """ + This is a very simple middleware that parses a request + and decides what translation object to install in the current + thread context. This allows pages to be dynamically + translated to the language the user desires (if the language + is available, of course). + """ + + def process_request(self, request): + user_language = 'ru_RU' + translation.activate(user_language) + request.session[translation.LANGUAGE_SESSION_KEY] = user_language diff --git a/archilance/mixins.py b/archilance/mixins.py index 0a80895..7305f6c 100644 --- a/archilance/mixins.py +++ b/archilance/mixins.py @@ -6,21 +6,20 @@ from django.views.generic.base import ContextMixin class BaseMixin(ContextMixin): def get_context_data(self, **kwargs): c = super().get_context_data(**kwargs) - - pk = self.kwargs.get('pk') # Current object's ID - next = self.kwargs.get('next') # Redirect next path - back = self.kwargs.get('back') # Redirect back path - + + pk = self.kwargs.get('pk') # Current object's ID + next = self.kwargs.get('next') # Redirect next path + back = self.kwargs.get('back') # Redirect back path + if pk: c['pk'] = int(pk) if next: c['next'] = next if back: c['back'] = back - + c['domain'] = Site.objects.get_current().domain - + c['TEMPLATE_DEBUG'] = getattr(settings, 'TEMPLATE_DEBUG', None) c['TESTING'] = getattr(settings, 'TESTING', None) - - return c + return c # import code; code.interact(local=dict(globals(), **locals())) diff --git a/archilance/settings/base.py b/archilance/settings/base.py index a56a662..ff03ede 100644 --- a/archilance/settings/base.py +++ b/archilance/settings/base.py @@ -83,6 +83,7 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + WAGTAIL MIDDLEWARE_CLASSES = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'archilance.middlewares.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -102,7 +103,6 @@ TEMPLATES = [ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(ROOT_DIR, 'templates')], 'APP_DIRS': True, - 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', @@ -237,7 +237,7 @@ FIELDS_STORED_IN_SESSION = ['user_type'] # Internationalization # https://docs.djangoproject.com/en/1.9/topics/i18n/ -LANGUAGE_CODE = 'ru-ru' +LANGUAGE_CODE = 'ru_RU' TIME_ZONE = 'Europe/Moscow' @@ -247,6 +247,10 @@ USE_L10N = True USE_TZ = True +LANGUAGES = ( + ('ru_RU', 'Russian'), +) + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.9/howto/static-files/ diff --git a/archilance/urls.py b/archilance/urls.py index 8dcf2d2..a81eb22 100644 --- a/archilance/urls.py +++ b/archilance/urls.py @@ -4,52 +4,50 @@ from django.conf.urls.static import static from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views.generic import TemplateView +from wagtail.wagtailadmin import urls as wagtailadmin_urls +from wagtail.wagtailcore import urls as wagtail_urls -from .views import HomeTemplateView, TestChatTemplateView, TestView from common.views import CustomRegistrationView from wallets.views import TmpCheckOrderView, TmpPaymentAvisoView +from .views import HomeTemplateView, TestChatTemplateView, TestView -from wagtail.wagtailadmin import urls as wagtailadmin_urls -from wagtail.wagtailcore import urls as wagtail_urls +urlpatterns = [ + url(r'^$', HomeTemplateView.as_view()), + url('', include('social.apps.django_app.urls', namespace='social')), + url(r'^chattest/$', TestChatTemplateView.as_view()), + url(r'^work_sell/', include('work_sell.urls')), + url(r'^test/$', TestView.as_view(), name='test'), + url(r'^projects/', include('projects.urls')), + url(r'^reviews/', include('reviews.urls')), + url(r'^wallets/', include('wallets.urls')), + url(r'^chat/', include('chat.urls')), + url(r'^specializations/', include('specializations.urls')), + url(r'^users/', include('users.urls')), + url(r'^common/', include('common.urls')), + url(r'^password/', include('password_reset.urls')), + url(r'^users/register/$', CustomRegistrationView.as_view(), name='registration_register'), + url(r'^users/', include('registration.backends.default.urls')), -urlpatterns = [ - url(r'^$', HomeTemplateView.as_view()), - url('', include('social.apps.django_app.urls', namespace='social')), - url(r'^chattest/$', TestChatTemplateView.as_view()), - url(r'^work_sell/', include('work_sell.urls')), - url(r'^test/$', TestView.as_view(), name='test'), - url(r'^projects/', include('projects.urls')), - url(r'^reviews/', include('reviews.urls')), - url(r'^wallets/', include('wallets.urls')), - url(r'^chat/', include('chat.urls')), - url(r'^specializations/', include('specializations.urls')), - url(r'^users/', include('users.urls')), - url(r'^common/', include('common.urls')), - url(r'^password/', include('password_reset.urls')), - - url(r'^users/register/$', CustomRegistrationView.as_view(), name='registration_register'), - url(r'^users/', include('registration.backends.default.urls')), - - url(r'^admin/', admin.site.urls), - url(r'^api/', include('api.urls')), - url(r'^cms/', include(wagtailadmin_urls)), - url(r'^pages/', include(wagtail_urls)), - - # TODO: Move URLs into the "wallets" app (Yandex Money settings modification needed): - - url(r'^yandex/check$', TmpCheckOrderView.as_view(), name='tmp-check-order'), - url(r'^yandex/aviso$', TmpPaymentAvisoView.as_view(), name='tmp-payment-aviso'), - - url(r'^payment/success$', TemplateView.as_view(template_name='message.html'), { - 'message': 'Оплата прошла успешно', - }, name='tmp-payment-success'), - - url(r'^payment/fail$', TemplateView.as_view(template_name='message.html'), { - 'message': 'Произошла ошибка при оплате', - }, name='tmp-payment-fail'), - -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + url(r'^admin/', admin.site.urls), + url(r'^api/', include('api.urls')), + url(r'^cms/', include(wagtailadmin_urls)), + url(r'^pages/', include(wagtail_urls)), + + # TODO: Move URLs into the "wallets" app (Yandex Money settings modification needed): + + url(r'^yandex/check$', TmpCheckOrderView.as_view(), name='tmp-check-order'), + url(r'^yandex/aviso$', TmpPaymentAvisoView.as_view(), name='tmp-payment-aviso'), + + url(r'^payment/success$', TemplateView.as_view(template_name='message.html'), { + 'message': 'Оплата прошла успешно', + }, name='tmp-payment-success'), + + url(r'^payment/fail$', TemplateView.as_view(template_name='message.html'), { + 'message': 'Произошла ошибка при оплате', + }, name='tmp-payment-fail'), + + ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: urlpatterns += staticfiles_urlpatterns() diff --git a/archilance/util.py b/archilance/util.py index 0deaf48..a673c07 100644 --- a/archilance/util.py +++ b/archilance/util.py @@ -1,12 +1,16 @@ +import datetime +from pprint import pprint + +import natsort +import pydash as _; from django.core import validators from django.core.exceptions import ObjectDoesNotExist from django.core.files.storage import FileSystemStorage from django.shortcuts import _get_queryset from django.utils import timezone -from pprint import pprint, pformat -import datetime -import natsort -import pydash as _; _.map = _.map_; _.filter = _.filter_ + +_.map = _.map_; +_.filter = _.filter_ import random import string @@ -20,20 +24,20 @@ def take(coll, n): def take_random(coll, n): if n == 0: return [] - + chunk = _.sample(coll, n) - + for item in chunk: coll.remove(item) - + return chunk def take_one_random(coll): if len(coll) == 0: return None - - return coll.pop(_.random(0, len(coll)-1)) + + return coll.pop(_.random(0, len(coll) - 1)) def random_phone(): @@ -62,7 +66,7 @@ def get_related_or_none(obj, attr): rel = getattr(obj, attr) except ObjectDoesNotExist: rel = None - + return rel @@ -75,20 +79,20 @@ def has_related(obj, attr): def get_attr_or_none(klass, *args, attr=None, **kwargs): object = get_or_none(klass, *args, **kwargs) - + if object and attr and isinstance(attr, str): return getattr(object, attr, None) def model_fields(model, width=200): fields = natsort.natsorted(model._meta.get_fields(), key=lambda f: f.name) - + pprint([( - f.name, - 'Relation? %s' % f.is_relation, - 'Null? %s' % getattr(f, 'null', None), - 'Blank? %s' % getattr(f, 'blank', None), - ) for f in fields], width=width) + f.name, + 'Relation? %s' % f.is_relation, + 'Null? %s' % getattr(f, 'null', None), + 'Blank? %s' % getattr(f, 'blank', None), + ) for f in fields], width=width) def lorem(words=None, sentences=5): @@ -101,58 +105,60 @@ def lorem(words=None, sentences=5): 'sit sollicitudin tempor tempus tincidunt tortor turpis ullamcorper ultricies ut varius vehicula vel velit vestibulum' 'vitae viverra volutpat vulputate' ), ' ') - - return _.join(_.times(lambda i_: _.capitalize(_.join(_.sample(vocab, words or _.random(5, 30)), ' ')), sentences), '. ') + + return _.join(_.times(lambda i_: _.capitalize(_.join(_.sample(vocab, words or _.random(5, 30)), ' ')), sentences), + '. ') def decap(s): if not isinstance(s, str): raise TypeError('String expected') - + return s[0].lower() + s[1:] if len(s) > 0 else s def random_ident(length=8): return ''.join( [random.choice(string.ascii_lowercase)] + - _.times(lambda x_: random.choice(string.ascii_letters + string.digits), length-1) + _.times(lambda x_: random.choice(string.ascii_letters + string.digits), length - 1) ) def validate_phone(text): text = text.replace(' ', '').replace('-', '') - + validate = validators.RegexValidator( regex=r'^((\+7|8)(\(\d{3}\)|(\d{3}))\d{7})$', message='Неверный номер телефона. Формат: +71112223344', ) - + validate(text) def to_local_datetime(obj): if not isinstance(obj, datetime.date): raise TypeError('Date expected') - + if not isinstance(obj, datetime.datetime): obj = datetime.datetime.combine(obj, datetime.time.min) - + if not getattr(obj, 'tzinfo', None): obj = timezone.utc.localize(obj) - + return obj def morph(number, words): CHOICES = (2, 0, 1, 1, 1, 2) - + if 4 < number % 100 < 20: choice = 2 else: choice = CHOICES[number % 10 if number % 10 < 5 else 5] - + return words[choice] + # # Example: # # words = ['яблоко', 'яблока', 'яблок'] @@ -164,8 +170,8 @@ def morph(number, words): class ASCIIFileSystemStorage(FileSystemStorage): def get_valid_name(self, name): symbols = (u"абвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ", - u"abvgdeejzijklmnoprstufhzcss_y_euaABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUA") - - tr = {ord(a):ord(b) for a, b in zip(*symbols)} + u"abvgdeejzijklmnoprstufhzcss_y_euaABVGDEEJZIJKLMNOPRSTUFHZCSS_Y_EUA") + + tr = {ord(a): ord(b) for a, b in zip(*symbols)} name = name.translate(tr) return super().get_valid_name(name) diff --git a/archilance/views.py b/archilance/views.py index 334f185..0ebff19 100644 --- a/archilance/views.py +++ b/archilance/views.py @@ -1,22 +1,15 @@ -from django.conf import settings -from django.contrib import messages -from django.core.exceptions import PermissionDenied +import pydash as _; from django.core.files.base import ContentFile -from django.core.urlresolvers import reverse, reverse_lazy -from django.http import HttpResponseForbidden, JsonResponse, HttpResponseRedirect, HttpResponse, Http404 -from django.shortcuts import render, get_object_or_404, redirect -from django.views.generic import TemplateView, View -from pprint import pprint, pformat -import json -import logging -import pydash as _; _.map = _.map_; _.filter = _.filter_ +from django.shortcuts import render +from django.views.generic import View + +_.map = _.map_; +_.filter = _.filter_ from .mixins import BaseMixin -from archilance import util from chat.models import Documents -from common.models import MainPage, PrintDocuments -from projects.models import Order -from users.models import User, ContractorResumeFiles +from common.models import MainPage +from users.models import User from work_sell.models import Picture @@ -25,18 +18,18 @@ class HomeTemplateView(BaseMixin, View): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - + main_settings = MainPage.objects.get(pk=1) context['main_settings'] = main_settings - + return render(request, self.template_name, context) class TestChatTemplateView(View): template_name = 'chat_test.html' - + def get(self, request, *args, **kwargs): - test = [54,55,56] + test = [54, 55, 56] for pk in test: picture = Picture.objects.get(pk=pk) temp_file = ContentFile(picture.file.read()) @@ -55,11 +48,10 @@ class TestView(BaseMixin, View): def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - + context['foo'] = _.flatten_deep(User.contractor_objects.order_by('?')[:10].values_list('pk')) - + return render(request, self.template_name, context) # return redirect('projects:detail', pk=153) - # import code; code.interact(local=dict(globals(), **locals())) diff --git a/archilance/wsgi.py b/archilance/wsgi.py index fb58beb..81d7bae 100644 --- a/archilance/wsgi.py +++ b/archilance/wsgi.py @@ -7,21 +7,23 @@ For more information on this file, see https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ """ +import os + from django.core.wsgi import get_wsgi_application from django.template.base import Variable -import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "archilance.settings.local") application = get_wsgi_application() - # Patch template Variable to output empty string for None values: old_resolve_lookup = Variable._resolve_lookup + def new_resolve_lookup(self, *args, **kwargs): val = old_resolve_lookup(self, *args, **kwargs) return '' if val == None else val + Variable._resolve_lookup = new_resolve_lookup diff --git a/assets/css/main.css b/assets/css/main.css index ed7a9fe..aad0124 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -1073,7 +1073,7 @@ footer:after { } .pagin nav ul li a { - background-color: none; + background-color: transparent; color: black; font-size: 20px; line-height: 50px; @@ -1099,7 +1099,6 @@ footer:after { .pagin nav ul li a:hover, .pagin nav ul li a:active { - color: black; color: white; background-color: #2c2c2c; border-color: #2c2c2c; @@ -2560,7 +2559,7 @@ input[type="checkbox"]:checked + span { text-align: center; margin: 0 0 0 0; position: relative; - letter-spacing: 0px; + letter-spacing: 0; } .compareBlock p:before { @@ -2986,9 +2985,8 @@ input[type="checkbox"]:checked + span { .compTable tr td:nth-child(6) ul li:nth-child(2):before { height: 20px; - background: url('../img/listTable.png') no-repeat center; background-size: cover; - background-position: 0 -17px; + background: url('../img/listTable.png') no-repeat 0 -17px; } .compTable tr td:nth-child(7) { @@ -3005,7 +3003,6 @@ input[type="checkbox"]:checked + span { font-weight: bold; font-family: Arial, Verdana, Helvetica, sans-serif; position: relative; - position: relative; width: 100%; float: left; } @@ -3839,7 +3836,7 @@ input[type="checkbox"]:checked + span { border-top: 1px solid black; border-bottom: 1px solid black; -webkit-transform: scale(1.03); - -moz- transform: scale(1.03); + -moz-transform: scale(1.03); transform: scale(1.03); box-shadow: 0 0 10px rgba(0,0,0,0.7); z-index: 999; @@ -3916,7 +3913,7 @@ input[type="checkbox"]:checked + span { border-top: 1px solid black; border-bottom: 1px solid black; -webkit-transform: scale(1.03); - -moz- transform: scale(1.03); + -moz-transform: scale(1.03); transform: scale(1.03); box-shadow: 0 0 10px rgba(0,0,0,0.7); z-index: 999; @@ -4286,7 +4283,6 @@ input[type="checkbox"]:checked + span { #modal_form1 { width: 600px; height: 400px; - display: table; position: fixed; top: 50%; left: 50%; @@ -4344,7 +4340,6 @@ input[type="checkbox"]:checked + span { width: 300px; height: 160px; min-height: 160px; - display: table; position: fixed; right: 0; top: 50%; @@ -5438,7 +5433,7 @@ input[type="radio"]:checked + span { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: bold; - margin: 0px 0 -15 0; + margin: 0px 0 -15px 0; } .inp-edit { @@ -5536,7 +5531,7 @@ input[type="radio"]:checked + span { .caret:after { content: "\e253"; position: absolute; - font-family: 'Glyphicons Halflings'; + font-family: 'Glyphicons Halflings', serif; right: -6.5px; top: -19px; font-size: 12px; @@ -6080,7 +6075,7 @@ input[type="radio"]{ .welcomeMain{ line-height: 48px; font-family: 'pfdintextcomppro-regular', sans-serif; - font-size: 43.5px; + font-size: 43px; } .menuUser > div{ padding: 10px 0; diff --git a/assets/img/skype.png b/assets/img/skype.png new file mode 100644 index 0000000..ee7d56b Binary files /dev/null and b/assets/img/skype.png differ diff --git a/assets/img/user-4.png b/assets/img/user-4.png new file mode 100644 index 0000000..7a0db8c Binary files /dev/null and b/assets/img/user-4.png differ diff --git a/assets/img/user-5.png b/assets/img/user-5.png new file mode 100644 index 0000000..e0a33e6 Binary files /dev/null and b/assets/img/user-5.png differ diff --git a/assets/index.js b/assets/index.js index 5f1fea7..cbe510c 100644 --- a/assets/index.js +++ b/assets/index.js @@ -180,10 +180,10 @@ $('[data-tooltip]').tooltip({ specSelects[2].select2(_.merge(specSelectOptions, {ajax: {url: specs.urlLevel3}})) specSelects[3].select2(_.merge(specSelectOptions, {ajax: {url: specs.urlLevel4}})) - var specLevel1 = specs.specLevel1 - var specLevel2 = specs.specLevel2 - var specLevel3 = specs.specLevel3 - var specLevel4 = specs.specLevel4 + var specLevel1 = specs.specLevel1; + var specLevel2 = specs.specLevel2; + var specLevel3 = specs.specLevel3; + var specLevel4 = specs.specLevel4; specSelects[0].select2('data', specLevel1 ? {id: specLevel1.id, text: specLevel1.name, origItem: specLevel1} : null) specSelects[1].select2('data', specLevel2 ? {id: specLevel2.id, text: specLevel2.name, origItem: specLevel2} : null) diff --git a/chat/admin.py b/chat/admin.py index 7a1f3ce..423d064 100644 --- a/chat/admin.py +++ b/chat/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from .models import Message, Notes, Documents, NewMessage @@ -11,11 +12,11 @@ class NotesAdmin(admin.ModelAdmin): class DocumentsAdmin(admin.ModelAdmin): - list_display = ('sender', 'recipent', 'order','team') + list_display = ('sender', 'recipent', 'order', 'team') class NewMessageAdmin(admin.ModelAdmin): - list_display = ('user', 'message_pk','message_recipent', 'message_order','message_team') + list_display = ('user', 'message_pk', 'message_recipent', 'message_order', 'message_team') def message_pk(self, obj): return obj.message.pk diff --git a/chat/chat.py b/chat/chat.py index 3da067e..12a5d93 100644 --- a/chat/chat.py +++ b/chat/chat.py @@ -1,13 +1,11 @@ import html -from tornado import gen, web, websocket, escape, options -from tornado.ioloop import IOLoop -from tornado.httpserver import HTTPServer -from tornado.options import parse_command_line -from settings import settings, PORT, DATABASE_DSN -import psycopg2 import momoko -import json +from settings import settings, PORT, DATABASE_DSN +from tornado import gen, web, websocket, escape +from tornado.httpserver import HTTPServer +from tornado.ioloop import IOLoop +from tornado.options import parse_command_line class BaseHandler(web.RequestHandler): @@ -91,8 +89,8 @@ class ChatHandler(websocket.WebSocketHandler): insert_sql = "INSERT INTO chat_message (id,text,created, sender_id,recipent_id," \ " private_type,team_id, order_id,is_delete,is_new) " \ - "VALUES (DEFAULT,'{0}',NOW(),{1},{2},{3},{4},{5},{6},{7}) RETURNING id".\ - format(message, sender_id, recipent_id, private_type, team_value,order_value, is_delete, is_new) + "VALUES (DEFAULT,'{0}',NOW(),{1},{2},{3},{4},{5},{6},{7}) RETURNING id". \ + format(message, sender_id, recipent_id, private_type, team_value, order_value, is_delete, is_new) cursor_list = yield dict(cursor=self.db.execute(insert_sql)) cursor = cursor_list.get('cursor') @@ -102,19 +100,20 @@ class ChatHandler(websocket.WebSocketHandler): team_ids = [t for t in team_ids_raw.rstrip(';').split(';')] values_str = ''; for t in team_ids: - values_str +='(DEFAULT,{0},{1}),'.format(message_id,t) + values_str += '(DEFAULT,{0},{1}),'.format(message_id, t) values_str = values_str.rstrip(',') - insert_new_messages = "INSERT INTO chat_newmessage (id,message_id, user_id) VALUES{0}".\ + insert_new_messages = "INSERT INTO chat_newmessage (id,message_id, user_id) VALUES{0}". \ format(values_str) else: - insert_new_messages = "INSERT INTO chat_newmessage (id,message_id, user_id) VALUES(DEFAULT,{0},{1})".\ + insert_new_messages = "INSERT INTO chat_newmessage (id,message_id, user_id) VALUES(DEFAULT,{0},{1})". \ format(message_id, recipent_id) yield self.db.execute(insert_new_messages) if docs_send_links: is_send = 'true' docs_send_ids = docs_send_links.rstrip(';').replace(';', ',') - update_sql_documents = "UPDATE chat_documents SET message_id={0},is_send={1} WHERE id IN({2})".format(message_id, is_send, docs_send_ids) + update_sql_documents = "UPDATE chat_documents SET message_id={0},is_send={1} WHERE id IN({2})".format( + message_id, is_send, docs_send_ids) yield self.db.execute(update_sql_documents) select_last_sql = "SELECT chat_message.id, chat_message.text, chat_message.created, chat_message.sender_id," \ @@ -143,7 +142,7 @@ class ChatHandler(websocket.WebSocketHandler): 'sender_name': sender_name, 'answer_type': answer_type, 'docs_attach': docs_attach, - }) + }) def check_origin(self, origin): return True diff --git a/chat/filters.py b/chat/filters.py index 0b3da56..ff4bb66 100644 --- a/chat/filters.py +++ b/chat/filters.py @@ -2,6 +2,7 @@ from rest_framework_filters import FilterSet, RelatedFilter, AllLookupsFilter from .models import Message, Notes, Documents + # # class DocumentsFilterSet(FilterSet): # file = AllLookupsFilter() @@ -20,7 +21,7 @@ class MessageFilterSet(FilterSet): id = AllLookupsFilter() private_type = AllLookupsFilter() text = AllLookupsFilter() - + order = RelatedFilter('projects.filters.OrderFilterSet') recipent = RelatedFilter('users.filters.UserFilterSet') sender = RelatedFilter('users.filters.UserFilterSet') @@ -30,22 +31,20 @@ class MessageFilterSet(FilterSet): model = Message - - class DocumentFilterSet(FilterSet): id = AllLookupsFilter() + # order = RelatedFilter('projects.filters.OrderFilterSet') class Meta: model = Documents - class NoteFilterSet(FilterSet): created = AllLookupsFilter() id = AllLookupsFilter() text = AllLookupsFilter() - + recipent = RelatedFilter('users.filters.UserFilterSet') sender = RelatedFilter('users.filters.UserFilterSet') diff --git a/chat/forms.py b/chat/forms.py index c65267a..9458bf6 100644 --- a/chat/forms.py +++ b/chat/forms.py @@ -4,4 +4,3 @@ from django import forms class ArticleForm(forms.Form): title = forms.CharField() pub_date = forms.DateField() - diff --git a/chat/models.py b/chat/models.py index 06c445d..bbe164a 100644 --- a/chat/models.py +++ b/chat/models.py @@ -8,7 +8,7 @@ from users.models import User, Team class Message(models.Model): text = models.TextField() created = models.DateTimeField(default=timezone.now) - order = models.ForeignKey(Order, related_name='messages',null=True, blank=True) + order = models.ForeignKey(Order, related_name='messages', null=True, blank=True) sender = models.ForeignKey(User, related_name='sender_messages') recipent = models.ForeignKey(User, related_name='recipent_messages') private_type = models.BooleanField(default=False) diff --git a/chat/response.py b/chat/response.py index e5e26f0..f428b75 100644 --- a/chat/response.py +++ b/chat/response.py @@ -1,4 +1,5 @@ import json + from django.http import HttpResponse MIMEANY = '*/*' diff --git a/chat/serializers.py b/chat/serializers.py index 3a38ce3..0ef0750 100644 --- a/chat/serializers.py +++ b/chat/serializers.py @@ -1,8 +1,8 @@ from rest_framework import serializers from rest_framework.serializers import ModelSerializer -from .models import Message, Notes, Documents from users.serializers import UserSerializer +from .models import Message, Notes, Documents class DocumentsSerializer(ModelSerializer): @@ -44,7 +44,6 @@ class MessageSerializer(ModelSerializer): documents = DocumentsSerializer(read_only=True, many=True) text = serializers.SerializerMethodField() - class Meta: model = Message @@ -62,14 +61,15 @@ class MessageSerializer(ModelSerializer): def get_text(self, obj): out = obj.text documents = obj.documents.all() - if len(documents)>0: - documents_str = '
'.join(['Приложенный файл. скачать:
' + doc.file.name + '' for doc in documents]) + if len(documents) > 0: + documents_str = '
'.join([ + 'Приложенный файл. скачать:
' + doc.file.name + '' + for doc in documents]) out += '

' + documents_str return out class NoteSerializer(ModelSerializer): - class Meta: model = Notes diff --git a/chat/settings.example.py b/chat/settings.example.py index 1878ce8..119a62d 100644 --- a/chat/settings.example.py +++ b/chat/settings.example.py @@ -13,7 +13,4 @@ settings = { 'server_traceback': True, } - DATABASE_DSN = 'dbname=archilance user=postgres password=postgres host=localhost' - - diff --git a/chat/tests.py b/chat/tests.py index 7ce503c..a39b155 100644 --- a/chat/tests.py +++ b/chat/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/chat/utils.py b/chat/utils.py index ec3e636..a780016 100644 --- a/chat/utils.py +++ b/chat/utils.py @@ -1,5 +1,4 @@ import mimetypes -from django.core.urlresolvers import reverse def serialize(instance, file_attr='file'): diff --git a/chat/views.py b/chat/views.py index 690b495..769b4c9 100644 --- a/chat/views.py +++ b/chat/views.py @@ -1,18 +1,19 @@ import json -from django.shortcuts import render +from wsgiref.util import FileWrapper + from django.conf import settings -from django.views.generic import View,CreateView -from django.http import HttpResponse, Http404 -from django.db.models import Q from django.contrib.auth.mixins import LoginRequiredMixin -from wsgiref.util import FileWrapper +from django.db.models import Q +from django.http import HttpResponse, Http404 +from django.shortcuts import render +from django.views.generic import View, CreateView -from .response import JSONResponse, response_mimetype -from .utils import serialize -from .models import Message, Documents, NewMessage from projects.models import Order, Project -from wallets.models import Transaction from users.models import User, Team +from wallets.models import Transaction +from .models import Message, Documents +from .response import JSONResponse, response_mimetype +from .utils import serialize class DocumentCreateView(CreateView): @@ -37,11 +38,11 @@ class ChatUserView(LoginRequiredMixin, View): template_name = '' def get(self, request, *args, **kwargs): - user_id = request.GET.get('user_id',None) + user_id = request.GET.get('user_id', None) if request.user.is_customer(): customer_contacts = Message.objects.values_list('sender_id', 'recipent_id'). \ - filter(Q(recipent_id=request.user.pk) | Q(sender_id=request.user.pk)).\ - filter(Q(team_id=None)).\ + filter(Q(recipent_id=request.user.pk) | Q(sender_id=request.user.pk)). \ + filter(Q(team_id=None)). \ filter(is_delete=False).distinct() users_ids = [] @@ -57,16 +58,18 @@ class ChatUserView(LoginRequiredMixin, View): contacts_users = User.objects.filter(pk__in=users_ids) archive_projects = request.user.customer_projects.select_related('order').exclude(state='active') chat_messages = Message.objects.filter(Q(sender=request.user.pk) | Q(recipent=request.user.pk)) - orders = request.user.customer_projects.select_related('order').filter(state='active').exclude(order__contractor__isnull=True, order__team__isnull=True) + orders = request.user.customer_projects.select_related('order').filter(state='active').exclude( + order__contractor__isnull=True, order__team__isnull=True) order_ids = [order.order.pk for order in orders] transaction = Transaction.objects.get_or_create(customer=request.user, type='reservation', complete=False) contacts_users_count = request.user.new_messages.filter(message__sender__in=users_ids, - message__order__isnull=True, - message__team__isnull=True - ).count() + message__order__isnull=True, + message__team__isnull=True + ).count() - orders_ms_count = request.user.new_messages.filter(message__order__in=order_ids, message__team__isnull=True).count() + orders_ms_count = request.user.new_messages.filter(message__order__in=order_ids, + message__team__isnull=True).count() self.template_name = 'chat_customer.html' return render(request, self.template_name, {'contacts_users': contacts_users, 'chat_messages': chat_messages, @@ -81,17 +84,19 @@ class ChatUserView(LoginRequiredMixin, View): team_ids = [] if request.user.is_owner_team(): team_ids.append(request.user.team.pk) - # team_orders = request.user.team.orders.all() + # team_orders = request.user.team.orders.all() # # teams = Team.objects.filter(contractors__id=request.user.pk).all() # else: teams = Team.objects.filter(Q(contractors__id=request.user.pk) | Q(owner=request.user.pk)).all() team_orders = Order.objects.filter(team_id__in=[team.pk for team in teams]).all() - orders = Order.objects.filter(Q(contractor=request.user) | Q(team_id__in=team_ids)).filter(project__state='active') - archive_orders = Order.objects.filter(Q(contractor=request.user) | Q(team_id__in=team_ids)).exclude(project__state='active') + orders = Order.objects.filter(Q(contractor=request.user) | Q(team_id__in=team_ids)).filter( + project__state='active') + archive_orders = Order.objects.filter(Q(contractor=request.user) | Q(team_id__in=team_ids)).exclude( + project__state='active') contractor_contacts = Message.objects.values_list('sender_id', 'recipent_id').filter( - Q(recipent_id=request.user.pk) | Q(sender_id=request.user.pk)).filter(Q(team_id=None)).\ + Q(recipent_id=request.user.pk) | Q(sender_id=request.user.pk)).filter(Q(team_id=None)). \ filter(is_delete=False).distinct() users_ids = [] for msg in contractor_contacts: @@ -105,15 +110,16 @@ class ChatUserView(LoginRequiredMixin, View): contacts_users = User.objects.filter(pk__in=users_ids) contacts_users_count = request.user.new_messages.filter(message__sender__in=users_ids, - message__order__isnull=True, - message__team__isnull=True - ).count() + message__order__isnull=True, + message__team__isnull=True + ).count() chat_messages = Message.objects.filter(Q(sender=request.user.pk) | Q(recipent=request.user.pk)).order_by( 'created') your_teams = Team.objects.filter(Q(contractors__id=request.user.pk) | Q(owner=request.user)) - orders_ms_count = request.user.new_messages.filter(message__order__in=orders, message__team__isnull=True).count() + orders_ms_count = request.user.new_messages.filter(message__order__in=orders, + message__team__isnull=True).count() teams_ms_count = request.user.new_messages.filter(message__team__in=your_teams).count() self.template_name = 'chat_contractor.html' @@ -146,7 +152,7 @@ def project_delete(request): if request.is_ajax(): project_id = request.POST.get('project_id') try: - project = Project.objects.get(pk=project_id,customer=request.user) + project = Project.objects.get(pk=project_id, customer=request.user) except Documents.DoesNotExist: project = None if project: @@ -160,7 +166,7 @@ def project_delete(request): raise Http404 -def download_file(request,file_name): +def download_file(request, file_name): try: document = Documents.objects.get(file=file_name) except Documents.DoesNotExist: diff --git a/common/admin.py b/common/admin.py index 47229b9..40bd9f2 100644 --- a/common/admin.py +++ b/common/admin.py @@ -1,7 +1,10 @@ +import pydash as _; from django import forms from django.contrib import admin from mptt.admin import MPTTModelAdmin -import pydash as _; _.map = _.map_; _.filter = _.filter_ + +_.map = _.map_; +_.filter = _.filter_ from .models import ( LiveImageUpload, @@ -21,17 +24,17 @@ class LocationAdmin(MPTTModelAdmin): class MainPageAdminForm(forms.ModelForm): class Meta: fields = '__all__' - + class Media: js = ( 'admin.js', 'lib/ckeditor/ckeditor.js', 'lib/ckeditor/adapters/jquery.js', ) - - def __init__(self, *args, **kwargs): + + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + for field_name in ('contractor_text', 'customer_text'): attrs = self.fields[field_name].widget.attrs attrs['class'] = _.join(_.compact((attrs.get('class'), '-ckeditor')), ' ') @@ -45,17 +48,17 @@ class MainPageAdmin(admin.ModelAdmin): class TooltipAdminForm(forms.ModelForm): class Meta: fields = '__all__' - + class Media: js = ( 'admin.js', 'lib/ckeditor/ckeditor.js', 'lib/ckeditor/adapters/jquery.js', ) - - def __init__(self, *args, **kwargs): + + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + for field_name in ('text', 'example'): attrs = self.fields[field_name].widget.attrs attrs['class'] = _.join(_.compact((attrs.get('class'), '-ckeditor')), ' ') diff --git a/common/filters.py b/common/filters.py index 17689f8..424ef3b 100644 --- a/common/filters.py +++ b/common/filters.py @@ -12,7 +12,7 @@ class LocationFilterSet(FilterSet): rght = AllLookupsFilter() tree_id = AllLookupsFilter() type = AllLookupsFilter() - + children = RelatedFilter('common.filters.LocationFilterSet') parent = RelatedFilter('common.filters.LocationFilterSet') @@ -24,6 +24,6 @@ class ContentTypeFilterSet(FilterSet): app_label = AllLookupsFilter() id = AllLookupsFilter() model = AllLookupsFilter() - + class Meta: model = ContentType diff --git a/common/forms.py b/common/forms.py index e0c6368..4d93dc2 100644 --- a/common/forms.py +++ b/common/forms.py @@ -1,10 +1,14 @@ +import pydash as _; from django import forms from registration.forms import RegistrationFormTermsOfService -import pydash as _; _.map = _.map_; _.filter = _.filter_ + +_.map = _.map_; +_.filter = _.filter_ from .models import PrintOrder, PrintDocuments from captcha.fields import ReCaptchaField + class PrintOrderForm(forms.ModelForm): files = forms.ModelMultipleChoiceField( queryset=PrintDocuments.objects.none(), @@ -37,18 +41,18 @@ class CustomRegistrationForm(RegistrationFormTermsOfService): ('customer', 'Заказчик'), ('contractor', 'Исполнитель'), ) - + user_type = forms.ChoiceField(choices=USER_TYPES) captcha = ReCaptchaField() - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - + if self.request.GET.get('type') == 'customer': self.fields['user_type'].initial = 'customer' elif self.request.GET.get('type') == 'contractor': self.fields['user_type'].initial = 'contractor' - + attrs = self.fields['user_type'].widget.attrs attrs['class'] = _.join(_.compact((attrs.get('class'), 'selectpicker3')), ' ') diff --git a/common/middleware.py b/common/middleware.py index 3124f39..65e6021 100644 --- a/common/middleware.py +++ b/common/middleware.py @@ -1,11 +1,12 @@ import datetime + from django.utils.timezone import now -from users.models import User + from ratings.models import HistoryRating +from users.models import User class SetLastVisitMiddleware(object): - def process_response(self, request, response): if hasattr(request, 'user'): if request.user.is_authenticated(): @@ -14,7 +15,6 @@ class SetLastVisitMiddleware(object): class SetRatingToUserEveryDay(object): - def process_response(self, request, response): if hasattr(request, 'user'): today_date = datetime.datetime.now().date() @@ -27,4 +27,3 @@ class SetRatingToUserEveryDay(object): hs_new.description = 'Балл за вход на сайт' hs_new.save() return response - diff --git a/common/models.py b/common/models.py index 90ca520..20e1eae 100644 --- a/common/models.py +++ b/common/models.py @@ -44,7 +44,7 @@ class Settings(models.Model): document_send_time_remove = models.IntegerField(default=14) recalculation_spec_time = models.TimeField() recalculation_rating_time = models.TimeField() - noreply_email = models.CharField(max_length=50,default='noreply@proekton.com') + noreply_email = models.CharField(max_length=50, default='noreply@proekton.com') def __str__(self): return 'Настройки сайта' @@ -93,16 +93,16 @@ class Tooltip(models.Model): ('top', 'Сверху'), ('bottom', 'Снизу'), ) - + name = models.CharField(max_length=255, unique=True) text = models.TextField(blank=True) example = models.TextField(blank=True) position = models.CharField(max_length=255, choices=POSITIONS, default='top') - + class Meta: verbose_name = 'Подсказка' verbose_name_plural = 'Подсказки' - + def __str__(self): return self.name @@ -110,10 +110,10 @@ class Tooltip(models.Model): class LiveImageUpload(models.Model): file = models.ImageField(upload_to='live-image-upload/') created_at = models.DateTimeField(default=timezone.now) - + class Meta: verbose_name = 'Живая загрузка изображений' verbose_name_plural = 'Живая загрузка изображений' - + def __str__(self): return self.file.url diff --git a/common/serializers.py b/common/serializers.py index 9c455e4..edc407e 100644 --- a/common/serializers.py +++ b/common/serializers.py @@ -7,7 +7,7 @@ from .models import Location class NestedLocationSerializer(ModelSerializer): class Meta: model = Location - + fields = ( 'children', 'id', @@ -24,7 +24,7 @@ class NestedLocationSerializer(ModelSerializer): class LocationSerializer(ModelSerializer): children = NestedLocationSerializer(many=True) parent = NestedLocationSerializer() - + class Meta: model = Location diff --git a/common/tests.py b/common/tests.py index 7ce503c..a39b155 100644 --- a/common/tests.py +++ b/common/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/common/urls.py b/common/urls.py index f4dd1c9..8332e29 100644 --- a/common/urls.py +++ b/common/urls.py @@ -12,7 +12,8 @@ app_name = 'common' urlpatterns = [ urls.url(r'^printdocument/create/$', PrintDocumentCreate.as_view(), name='create'), urls.url(r'^printorder/(?P\d+)/$', PrintOrderDetailView.as_view(), name='print-order-detail'), - + urls.url(r'^live-image-upload/create/$', LiveImageUploadCreateView.as_view(), name='live-image-upload-create'), - urls.url(r'^live-image-upload/(?P\d+)/delete/$', LiveImageUploadDeleteView.as_view(), name='live-image-upload-delete'), + urls.url(r'^live-image-upload/(?P\d+)/delete/$', LiveImageUploadDeleteView.as_view(), + name='live-image-upload-delete'), ] diff --git a/common/views.py b/common/views.py index 9768201..e4a5a55 100644 --- a/common/views.py +++ b/common/views.py @@ -1,8 +1,7 @@ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import Group -from django.core.exceptions import PermissionDenied -from django.core.mail import send_mail, EmailMultiAlternatives +from django.core.mail import EmailMultiAlternatives from django.core.urlresolvers import reverse, reverse_lazy from django.http import JsonResponse from django.shortcuts import render, redirect @@ -10,15 +9,13 @@ from django.template.loader import get_template, render_to_string from django.views.generic import View, DetailView from registration.backends.default.views import RegistrationView from sorl.thumbnail import get_thumbnail -import json -import jsonschema -from .forms import PrintOrderForm, CustomRegistrationForm -from .models import PrintDocuments, PrintOrder, Settings, LiveImageUpload from archilance import util from archilance.mixins import BaseMixin from common.mixins import NoCsrfMixin from users.models import ContractorResume +from .forms import PrintOrderForm, CustomRegistrationForm +from .models import PrintDocuments, PrintOrder, Settings, LiveImageUpload class PrintOrderDetailView(DetailView): @@ -62,7 +59,6 @@ class PrintDocumentCreate(BaseMixin, View): 'files': link_files, } - settings = Settings.objects.all().first() subject, from_email, to = 'Заявка на распечатку', settings.noreply_email, settings.document_send_email text_content = render_to_string('document_email.txt', ctx_dict) @@ -74,7 +70,7 @@ class PrintDocumentCreate(BaseMixin, View): msg.send() messages.info(request, 'Заявка на распечатку принята') - return redirect('common:print-order-detail',pk=print_order.pk) + return redirect('common:print-order-detail', pk=print_order.pk) # import code; code.interact(local=dict(globals(), **locals())) else: context = self.get_context_data(**kwargs) @@ -82,16 +78,15 @@ class PrintDocumentCreate(BaseMixin, View): return render(request, self.template_name, context) - class LiveImageUploadCreateView(NoCsrfMixin, LoginRequiredMixin, View): def post(self, request, *args, **kwargs): image = request.FILES.get('image') - + if not image: return JsonResponse({'files': [{'error': 'No image provided'}]}) - + live_img = LiveImageUpload.objects.create(file=image) - + return JsonResponse({ 'id': live_img.pk, 'name': live_img.file.name, @@ -107,7 +102,7 @@ class LiveImageUploadCreateView(NoCsrfMixin, LoginRequiredMixin, View): class LiveImageUploadDeleteView(NoCsrfMixin, LoginRequiredMixin, View): def post(self, request, *args, **kwargs): live_img = util.get_or_none(LiveImageUpload, pk=kwargs.get('pk')) - + if live_img: live_img.file.delete() live_img.delete() @@ -120,38 +115,37 @@ class CustomRegistrationView(RegistrationView): form_class = CustomRegistrationForm template_name = 'registration/registration_form.html' success_url = reverse_lazy('registration_complete') - + def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['request'] = self.request return kwargs - + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - + if self.request.GET.get('type') in ('customer', 'contractor'): context['hide_user_type'] = True - + return context - + def register(self, form): user = super().register(form) - + if form.cleaned_data.get('user_type') == 'customer': group = Group.objects.get(name='Заказчики') elif form.cleaned_data.get('user_type') == 'contractor': group = Group.objects.get(name='Исполнители') else: group = None - + if group: user.groups.add(group) - + user.contractor_resume = ContractorResume.objects.create(text='') - + user.save() - - return user + return user # import code; code.interact(local=dict(globals(), **locals())) diff --git a/projects/admin.py b/projects/admin.py index 475af3d..008efe3 100644 --- a/projects/admin.py +++ b/projects/admin.py @@ -25,17 +25,17 @@ class BuildingClassficationAdmin(MPTTModelAdmin): class ProjectAdminForm(forms.ModelForm): files = forms.ModelMultipleChoiceField(queryset=ProjectFile.objects.none(), required=False) - + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - + if self.instance.pk: self.fields['files'].queryset = self.instance.files class ProjectAdmin(admin.ModelAdmin): readonly_fields = ('pk',) - list_display = ('name','pk','customer','state') + list_display = ('name', 'pk', 'customer', 'state') form = ProjectAdminForm @@ -44,7 +44,7 @@ class OrderAdmin(admin.ModelAdmin): class StageAdmin(admin.ModelAdmin): - list_display = ('name','status','pos','order','is_paid',) + list_display = ('name', 'status', 'pos', 'order', 'is_paid',) admin.site.register(Answer) diff --git a/projects/apps.py b/projects/apps.py index 95e9770..d48658a 100644 --- a/projects/apps.py +++ b/projects/apps.py @@ -6,5 +6,4 @@ class ProjectsConfig(AppConfig): name = 'projects' def ready(self): - import projects.signals - + pass diff --git a/projects/filters.py b/projects/filters.py index b111a72..d05239d 100755 --- a/projects/filters.py +++ b/projects/filters.py @@ -16,7 +16,7 @@ from .models import ( class BuildingClassficationFilterSet(FilterSet): id = AllLookupsFilter() name = AllLookupsFilter() - + class Meta: model = BuildingClassfication @@ -24,7 +24,7 @@ class BuildingClassficationFilterSet(FilterSet): class ConstructionTypeFilterSet(FilterSet): id = AllLookupsFilter() name = AllLookupsFilter() - + class Meta: model = ConstructionType @@ -44,13 +44,13 @@ class ProjectFilterSet(FilterSet): term_type = AllLookupsFilter() 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') - + class Meta: model = Project @@ -66,16 +66,16 @@ class AnswerFilterSet(FilterSet): 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 @@ -85,7 +85,7 @@ class OrderFilterSet(FilterSet): id = AllLookupsFilter() secure = AllLookupsFilter() status = AllLookupsFilter() - + contractor = RelatedFilter('users.filters.UserFilterSet') project = RelatedFilter('projects.filters.ProjectFilterSet') team = RelatedFilter('users.filters.TeamFilterSet') @@ -105,7 +105,7 @@ class StageFilterSet(FilterSet): status = AllLookupsFilter() term = AllLookupsFilter() term_type = AllLookupsFilter() - + order = RelatedFilter('projects.filters.OrderFilterSet') class Meta: @@ -115,22 +115,23 @@ class StageFilterSet(FilterSet): class RealtyFilterSet(FilterSet): id = AllLookupsFilter() name = AllLookupsFilter() - + building_classification = RelatedFilter('projects.filters.BuildingClassficationFilterSet') construction_type = RelatedFilter('projects.filters.ConstructionTypeFilterSet') location = RelatedFilter('common.filters.LocationFilterSet') user = RelatedFilter('users.filters.UserFilterSet') - + class Meta: model = Realty class PortfolioPhotoFilterSet(FilterSet): id = AllLookupsFilter() - + portfolio = RelatedFilter('projects.filters.PortfolioFilterSet') + # img = ??? - + class Meta: model = PortfolioPhoto @@ -142,9 +143,9 @@ class PortfolioFilterSet(FilterSet): name = AllLookupsFilter() term = AllLookupsFilter() term_type = AllLookupsFilter() - + photos = RelatedFilter('projects.filters.PortfolioPhotoFilterSet') user = RelatedFilter('users.filters.UserFilterSet') - + class Meta: model = Portfolio diff --git a/projects/forms.py b/projects/forms.py index cb3919d..5778d5a 100644 --- a/projects/forms.py +++ b/projects/forms.py @@ -1,10 +1,13 @@ +import itertools + +import pydash as _; from django import forms from django.db.models import Q from django.forms.models import inlineformset_factory from mptt.forms import TreeNodeChoiceField -from pprint import pprint, pformat -import itertools -import pydash as _; _.map = _.map_; _.filter = _.filter_ + +_.map = _.map_; +_.filter = _.filter_ from .models import ( Project, ProjectFile, Portfolio, Answer, @@ -14,47 +17,47 @@ from .models import ( from archilance import util from common.models import Location, LiveImageUpload from specializations.models import Specialization -from users.models import User, Team +from users.models import Team class ProjectFilterForm(forms.ModelForm): - PROJECT_ORDER_CHOICES = ( # "Упорядочить по"... + PROJECT_ORDER_CHOICES = ( # "Упорядочить по"... ('name', 'названию'), ('budget', 'цене'), ('created', 'дате размещения'), ('views', 'просмотрам'), ) - + order_by = forms.ChoiceField(required=False, choices=PROJECT_ORDER_CHOICES) last_order_by = forms.ChoiceField(required=False, choices=PROJECT_ORDER_CHOICES) reverse_order = forms.BooleanField(required=False) keywords = forms.CharField(required=False, max_length=255) - + class Meta: model = Project - + fields = ( 'cro', 'work_type', - + 'realty', 'specialization', ) - + widgets = { 'work_type': forms.Select(attrs={'class': 'selectpicker6 -project-work-type-select-field'}), } - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - - self.fields['work_type'].choices = tuple(itertools.chain((('',''),), self.fields['work_type'].choices)) + + self.fields['work_type'].choices = tuple(itertools.chain((('', ''),), self.fields['work_type'].choices)) self.fields['work_type'].required = False self.fields['work_type'].initial = '' - + self.fields['specialization'].required = False - + self.fields['specialization'].queryset = Specialization.objects.root_nodes()[0].get_descendants() # self.fields['specialization'].queryset = Specialization.objects # Migrate with this enabled @@ -62,45 +65,46 @@ class ProjectFilterForm(forms.ModelForm): class ProjectFilterRealtyForm(forms.ModelForm): class Meta: model = Realty - + fields = ( 'building_classification', 'construction_type', 'location', ) - + widgets = { 'building_classification': forms.Select(attrs={'class': 'selectpicker'}), 'construction_type': forms.Select(attrs={'class': 'selectpicker'}), } - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - + self.fields['building_classification'].empty_label = '' self.fields['building_classification'].required = False - + self.fields['construction_type'].empty_label = '' self.fields['construction_type'].required = False - + self.fields['location'].queryset = Location.objects.root_nodes()[0].get_descendants() - self.fields['building_classification'].queryset = BuildingClassfication.objects.root_nodes()[0].get_descendants() + self.fields['building_classification'].queryset = BuildingClassfication.objects.root_nodes()[ + 0].get_descendants() # self.fields['location'].queryset = Location.objects # Migrate with this enabled class CustomerProjectEditForm(forms.ModelForm): # Define a form field manually for a reverse model vield: - + files = forms.ModelMultipleChoiceField( queryset=ProjectFile.objects.none(), widget=forms.CheckboxSelectMultiple, required=False, ) - + class Meta: model = Project - + fields = ( 'budget', 'budget_by_agreement', @@ -116,38 +120,38 @@ class CustomerProjectEditForm(forms.ModelForm): 'text', 'work_type', ) - + widgets = { 'currency': forms.Select(attrs={'class': 'selectpicker2 valul'}), 'realty': forms.Select(attrs={'class': 'selectpicker'}), 'term_type': forms.Select(attrs={'class': 'selectpicker'}), } - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - + self.fields['realty'].empty_label = 'Создать новый' self.fields['realty'].queryset = self.request.user.realties.all() - + self.fields['budget'].required = False self.fields['currency'].required = False self.fields['term_type'].required = False - + self.fields['specialization'].queryset = Specialization.objects.root_nodes()[0].get_descendants() # self.fields['specialization'].queryset = Specialization.objects # Migrate with this enabled - + if self.instance.pk: self.fields['files'].queryset = self.instance.files - + def clean(self): cleaned_data = super().clean() - + if not cleaned_data.get('budget_by_agreement'): if not cleaned_data.get('budget'): raise forms.ValidationError({'budget': 'Это поле обязательно'}) if not cleaned_data.get('currency'): raise forms.ValidationError({'currency': 'Это поле обязательно'}) if not cleaned_data.get('term_type'): raise forms.ValidationError({'term_type': 'Это поле обязательно'}) - + return cleaned_data @@ -164,27 +168,28 @@ class RealtyForm(forms.ModelForm): class Meta: model = Realty - + fields = ( 'building_classification', 'construction_type', 'location', 'name', ) - + widgets = { 'construction_type': forms.Select(attrs={'class': 'selectpicker'}), } - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - + if not self.data.get('extra_fields'): self.fields['name'].required = False - + self.fields['location'].queryset = Location.objects.root_nodes()[0].get_descendants() - self.fields['building_classification'].queryset = BuildingClassfication.objects.root_nodes()[0].get_descendants() + self.fields['building_classification'].queryset = BuildingClassfication.objects.root_nodes()[ + 0].get_descendants() # self.fields['location'].queryset = Location.objects # Migrate with this enabled @@ -200,7 +205,7 @@ class PortfolioForm(forms.ModelForm): class Meta: model = Portfolio fields = '__all__' - + widgets = { 'construction_type': forms.Select(attrs={'class': 'selectpicker'}), 'currency': forms.Select(attrs={'class': 'selectpicker'}), @@ -211,13 +216,13 @@ class PortfolioForm(forms.ModelForm): class PortfolioEditForm(forms.ModelForm): # Define a form field manually for a reverse model vield: - + photos = forms.ModelMultipleChoiceField( queryset=PortfolioPhoto.objects.none(), widget=forms.CheckboxSelectMultiple, required=False, ) - + live_images = forms.ModelMultipleChoiceField( queryset=LiveImageUpload.objects.all(), widget=forms.CheckboxSelectMultiple, @@ -229,15 +234,15 @@ class PortfolioEditForm(forms.ModelForm): widget=forms.Select(attrs={'class': 'selectpicker'}), level_indicator='  ', ) - + class Meta: model = Portfolio - + fields = ( 'building_classification', 'construction_type', 'specialization', - + 'budget', 'currency', 'description', @@ -247,27 +252,27 @@ class PortfolioEditForm(forms.ModelForm): 'work_type', 'worksell', ) - + widgets = { 'construction_type': forms.Select(attrs={'class': 'selectpicker'}), 'currency': forms.Select(attrs={'class': 'selectpicker'}), 'term_type': forms.Select(attrs={'class': 'selectpicker'}), 'work_type': forms.Select(attrs={'class': 'selectpicker -project-work-type-select-field'}), } - + def __init__(self, *args, **kwargs): # self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - + self.fields['photos'].queryset = self.instance.photos.all() class ProjectAnswerForm(forms.ModelForm): text = forms.CharField(widget=forms.Textarea) - + class Meta: model = Answer - + fields = ( 'budget', 'currency', @@ -276,61 +281,62 @@ class ProjectAnswerForm(forms.ModelForm): 'term', 'term_type', ) - + widgets = { 'currency': forms.Select(attrs={'class': 'selectpicker'}), 'term_type': forms.Select(attrs={'class': 'selectpicker'}), } - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') self.project = kwargs.pop('project') self.answer_as_team = kwargs.pop('answer_as_team', None) super().__init__(*args, **kwargs) - + if self.answer_as_team: self.fields['secure_deal_only'].label = 'Работаем только по безопасной сделке' - + contractor = self.request.user members = contractor.team.contractors.all() if util.has_related(contractor, 'team') else () - + # portfolios = [] # portfolios.extend(contractor.portfolios.all()) # # for c in members: # portfolios.extend(c.portfolios.all()) - - portfolios = Portfolio.objects.filter(user__pk__in=tuple(m.pk for m in itertools.chain((contractor,), members))) - + + portfolios = Portfolio.objects.filter( + user__pk__in=tuple(m.pk for m in itertools.chain((contractor,), 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): class Meta: model = AnswerMessage - + fields = ( 'text', ) - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) @@ -339,7 +345,7 @@ class ProjectAnswerMessageForm(forms.ModelForm): class StageForm(forms.ModelForm): class Meta: model = Stage - + fields = ( # 'cost', # 'cost_type', @@ -355,11 +361,11 @@ PortfolioPhotoFormSet = inlineformset_factory(Portfolio, PortfolioPhoto, fields= class CustomerProjectTrashForm(forms.Form): pk = forms.ModelChoiceField(queryset=Project.objects.none()) - + def __init__(self, *args, **kwargs): self.req = kwargs.pop('req') super().__init__(*args, **kwargs) - + self.fields['pk'].queryset = self.req.user.customer_projects.filter(state='active') @@ -369,52 +375,51 @@ class ContractorPortfolioTrashForm(forms.Form): def __init__(self, *args, **kwargs): self.request = kwargs.pop('request') super().__init__(*args, **kwargs) - + self.fields['pk'].queryset = self.request.user.portfolios.all() class CustomerProjectRestoreForm(forms.Form): pk = forms.ModelChoiceField(queryset=Project.objects.none()) - + def __init__(self, *args, **kwargs): self.req = kwargs.pop('req') super().__init__(*args, **kwargs) - + self.fields['pk'].queryset = self.req.user.customer_projects.filter(state='trashed') class CustomerProjectDeleteForm(forms.Form): pk = forms.ModelChoiceField(queryset=Project.objects.none()) - + def __init__(self, *args, **kwargs): self.req = kwargs.pop('req') super().__init__(*args, **kwargs) - + self.fields['pk'].queryset = self.req.user.customer_projects.filter(Q(state='active') | Q(state='trashed')) class ProjectWorkTypeSuggestionForm(forms.ModelForm): class Meta: model = ProjectWorkTypeSuggestion - + fields = ( 'name', 'commentary', 'email', 'username', ) - + def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) super().__init__(*args, **kwargs) - + # for bfield in self: # attrs = bfield.field.widget.attrs # attrs['class'] = _.join(_.compact((attrs.get('class'), '-error -error-%s' % bfield.html_name)), ' ') - + if self.request and self.request.user.is_authenticated(): self['username'].field.initial = self.request.user.username self['email'].field.initial = self.request.user.email - # import code; code.interact(local=dict(globals(), **locals())) diff --git a/projects/locale/ru_RU/LC_MESSAGES/django.mo b/projects/locale/ru_RU/LC_MESSAGES/django.mo new file mode 100644 index 0000000..bec658f Binary files /dev/null and b/projects/locale/ru_RU/LC_MESSAGES/django.mo differ diff --git a/projects/locale/ru_RU/LC_MESSAGES/django.po b/projects/locale/ru_RU/LC_MESSAGES/django.po new file mode 100644 index 0000000..f079538 --- /dev/null +++ b/projects/locale/ru_RU/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-10-14 22:49+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/_trash/project_form.html:46 +#: templates/contractor_portfolio_edit.html:46 +#: templates/customer_project_create.html:62 +#: templates/customer_project_edit.html:72 templates/project_filter.html:21 +msgid "project_stage0" +msgstr "Тип работы" + +#: templates/contractor_portfolio_edit.html:59 +#: templates/customer_project_create.html:89 +#: templates/customer_project_edit.html:99 templates/project_filter.html:35 +msgid "project_stage1" +msgstr "Стадия проекта" + +#: templates/contractor_portfolio_edit.html:64 +#: templates/customer_project_create.html:94 +#: templates/customer_project_edit.html:104 templates/project_filter.html:39 +msgid "project_stage2" +msgstr "Раздел" + +#: templates/contractor_portfolio_edit.html:69 +#: templates/customer_project_create.html:99 +#: templates/customer_project_edit.html:109 templates/project_filter.html:43 +msgid "project_stage3" +msgstr "Подраздел" + +#: templates/contractor_portfolio_edit.html:74 +#: templates/customer_project_create.html:104 +#: templates/customer_project_edit.html:114 templates/project_filter.html:47 +msgid "project_stage4" +msgstr "Подраздел ( доп. )" diff --git a/projects/migrations/0047_auto_20161014_2349.py b/projects/migrations/0047_auto_20161014_2349.py new file mode 100644 index 0000000..d37c33b --- /dev/null +++ b/projects/migrations/0047_auto_20161014_2349.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-14 20:49 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('projects', '0046_merge'), + ] + + operations = [ + migrations.AlterModelOptions( + name='buildingclassfication', + options={'ordering': ['order'], 'verbose_name': 'Тип здания', 'verbose_name_plural': 'Типы зданий'}, + ), + migrations.AlterModelOptions( + name='constructiontype', + options={'ordering': ['order'], 'verbose_name': 'Вид строительства', 'verbose_name_plural': 'Виды строительства'}, + ), + migrations.AlterModelManagers( + name='buildingclassfication', + managers=[ + ], + ), + migrations.AddField( + model_name='buildingclassfication', + name='order', + field=models.PositiveSmallIntegerField(default=0, null=True), + ), + migrations.AddField( + model_name='constructiontype', + name='order', + field=models.PositiveSmallIntegerField(default=0, null=True), + ), + ] diff --git a/projects/models.py b/projects/models.py index b809d76..8e1c488 100644 --- a/projects/models.py +++ b/projects/models.py @@ -1,17 +1,19 @@ +import pydash as _; from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import Q -from django.core.exceptions import ValidationError from django.utils import timezone from hitcount.models import HitCountMixin +from mptt.managers import TreeManager from mptt.models import TreeForeignKey, MPTTModel -import pydash as _; _.map = _.map_; _.filter = _.filter_ + +_.map = _.map_; +_.filter = _.filter_ from users.models import User, Team from specializations.models import Specialization - CURRENCIES = ( ('rur', 'RUR'), ('usd', 'USD'), @@ -36,22 +38,31 @@ TERM_TYPE_MORPHS = { class BuildingClassfication(MPTTModel): name = models.CharField(max_length=255) parent = TreeForeignKey('self', blank=True, null=True, related_name='children', db_index=True) + order = models.PositiveSmallIntegerField(default=0, null=True) + + objects = TreeManager() def __str__(self): return self.name + class MPTTMeta: + order_insertion_by = ['order'] + class Meta: + ordering = ['order'] verbose_name = 'Тип здания' verbose_name_plural = 'Типы зданий' class ConstructionType(models.Model): name = models.CharField(max_length=255) + order = models.PositiveSmallIntegerField(default=0, null=True) def __str__(self): return self.name class Meta: + ordering = ['order'] verbose_name = 'Вид строительства' verbose_name_plural = 'Виды строительства' @@ -61,7 +72,7 @@ class Realty(models.Model): construction_type = models.ForeignKey(ConstructionType, related_name='realties') location = TreeForeignKey('common.Location', related_name='realties', null=True, blank=True) name = models.CharField(max_length=255) - user = models.ForeignKey(User, related_name='realties') # Do we actually need this field? + user = models.ForeignKey(User, related_name='realties') # Do we actually need this field? def __str__(self): return self.name @@ -77,18 +88,18 @@ class Project(models.Model, HitCountMixin): (2, 'Техническое сопровождение'), # (3, 'Устранение замечаний в проекте'), ) - + DEAL_TYPES = ( - ('secure_deal', 'Безопасная сделка'), # "Безопасная сделка (с резервированием бюджета)" - ('direct_payment', 'Прямая оплата'), # "Прямая оплата Исполнителю на его кошелек/счет" + ('secure_deal', 'Безопасная сделка'), # "Безопасная сделка (с резервированием бюджета)" + ('direct_payment', 'Прямая оплата'), # "Прямая оплата Исполнителю на его кошелек/счет" ) - + STATES = ( ('active', 'Активный'), ('trashed', 'В корзине'), ('deleted', 'Удален'), ) - + budget = models.DecimalField(max_digits=10, decimal_places=0) budget_by_agreement = models.BooleanField(default=False) created = models.DateTimeField(default=timezone.now) @@ -105,18 +116,18 @@ class Project(models.Model, HitCountMixin): term_type = models.CharField(max_length=20, choices=TERM_TYPES, default='hour') text = models.TextField(blank=True) work_type = models.IntegerField(default=1, choices=WORK_TYPES) - + def __str__(self): return self.name - + class Meta: verbose_name = 'Проект' verbose_name_plural = 'Проекты' ordering = ('-created',) - + def secure_deal(self): return self.deal_type == 'secure_deal' - + def get_team_answers(self): return _.filter(self.answers.all(), lambda a: isinstance(a.author, Team)) @@ -124,11 +135,11 @@ class Project(models.Model, HitCountMixin): class ProjectFile(models.Model): file = models.FileField(upload_to='projects/project_files/') project = models.ForeignKey(Project, related_name='files', blank=True, null=True) - + class Meta: verbose_name = 'Файл проекта' verbose_name_plural = 'Файлы проектов' - + def __str__(self): return self.file and self.file.url or self.pk @@ -137,27 +148,29 @@ class Answer(models.Model): 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, 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') secure_deal_only = models.BooleanField(default=False) term = models.IntegerField(blank=True, null=True) term_type = models.CharField(max_length=10, choices=TERM_TYPES, blank=True, null=True) is_archive = models.BooleanField(default=False) rejected = models.BooleanField(default=False) - - 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() author = GenericForeignKey('content_type', 'object_id') def get_first_message(self): message = self.messages.first() - + if message: return message.text - + def __str__(self): return "{author}'s answer ({id})".format(author=type(self.author).__name__, id=self.pk) - + class Meta: verbose_name = 'Отклик на проект' verbose_name_plural = 'Отклики на проекты' @@ -169,27 +182,27 @@ class AnswerMessage(models.Model): # # customer: `message.is_sender_customer and message.answer.project.customer` # contractor/team: `not message2.is_sender_customer and message2.contractor_or_team` - + answer = models.ForeignKey(Answer, related_name='messages') created = models.DateTimeField(default=timezone.now) is_sender_customer = models.BooleanField(default=False) seen_by_contractor = models.BooleanField(default=False) seen_by_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'), null=True, blank=True, ) - + object_id = models.IntegerField(null=True, blank=True) contractor_or_team = GenericForeignKey('content_type', 'object_id') - + def __str__(self): return str(self.pk) - + class Meta: ordering = ['created'] verbose_name = 'Отклики на проекты -- переписка' @@ -200,11 +213,11 @@ 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/') - + class Meta: verbose_name = 'Файл для отклика' verbose_name_plural = 'Файлы для откликов' - + def __str__(self): return self.file and self.file.url or self.pk @@ -215,17 +228,18 @@ class Order(models.Model): ('process', 'В процессе'), ('completed', 'Завершен'), ) - - contractor = models.ForeignKey(User, null=True, blank=True, related_name='orders') # Related name should've been "contractor_orders" + + contractor = models.ForeignKey(User, null=True, blank=True, + related_name='orders') # Related name should've been "contractor_orders" created = models.DateTimeField(default=timezone.now) project = models.OneToOneField(Project, related_name='order') secure = models.BooleanField(default=False) status = models.CharField(max_length=30, choices=STATUSES, default='created') team = models.ForeignKey(Team, null=True, blank=True, related_name='orders') - + def __str__(self): return self.project.name - + class Meta: verbose_name = 'Заказ' verbose_name_plural = 'Заказы' @@ -238,6 +252,7 @@ class Order(models.Model): else: return None + class Arbitration(models.Model): user = models.ForeignKey(User) text = models.TextField() @@ -252,7 +267,7 @@ class Arbitration(models.Model): verbose_name_plural = 'Арбитраж' unique_together = ( - ('user', 'order'), + ('user', 'order'), ) @@ -264,7 +279,6 @@ STATUSES = ( ('completed', 'Завершен'), ) -from .validators import validate_term class Stage(models.Model): cost = models.DecimalField(max_digits=10, decimal_places=0) @@ -304,8 +318,9 @@ class Stage(models.Model): class Candidate(models.Model): - answer = models.ForeignKey(Answer, related_name='candidates') # TODO: Swap to "OneToOneField" - project = models.ForeignKey(Project, related_name='candidates') # TODO: Remove this redundant field at all (we've got "candidate.answer.project") + answer = models.ForeignKey(Answer, related_name='candidates') # TODO: Swap to "OneToOneField" + project = models.ForeignKey(Project, + related_name='candidates') # TODO: Remove this redundant field at all (we've got "candidate.answer.project") status = models.BooleanField(default=False) position = models.PositiveIntegerField(default=0) @@ -356,17 +371,17 @@ class Portfolio(models.Model): def get_cover(self): photo = self.photos.first() - return photo and photo.img # WTF? We could at leat return a URL, not an object + return photo and photo.img # WTF? We could at leat return a URL, not an object class PortfolioPhoto(models.Model): img = models.ImageField(upload_to='projects/portfolio/') portfolio = models.ForeignKey(Portfolio, related_name='photos') - + class Meta: verbose_name = 'Фото портфолио' verbose_name_plural = 'Фото портфолио' - + def __str__(self): return self.img and self.img.url or str(self.img) @@ -377,10 +392,10 @@ class ProjectWorkTypeSuggestion(models.Model): processed = models.BooleanField(default=False, verbose_name='Рассмотрено?') username = models.CharField(max_length=255, blank=True, verbose_name='Имя пользователя') email = models.EmailField(verbose_name='Email') - + class Meta: verbose_name = 'Предложение типов работ (проектов)' verbose_name_plural = 'Предложения типов работ (проектов)' - + def __str__(self): return self.name diff --git a/projects/serializers.py b/projects/serializers.py index 33ffff6..4106bb5 100755 --- a/projects/serializers.py +++ b/projects/serializers.py @@ -1,20 +1,21 @@ -from rest_framework import serializers from generic_relations.relations import GenericRelatedField -from rest_framework.serializers import ModelSerializer, ImageField, FileField, SerializerMethodField, PrimaryKeyRelatedField, ReadOnlyField +from rest_framework import serializers +from rest_framework.serializers import ModelSerializer, ImageField, FileField, PrimaryKeyRelatedField -from .models import Project, Realty, BuildingClassfication, ConstructionType, Order, Stage, Portfolio, PortfolioPhoto, Answer, AnswerFile from common.serializers import LocationSerializer, ContentTypeSerializer from specializations.serializers import SpecializationSerializer from users.models import User, Team from users.serializers import UserSerializer, TeamSerializer +from .models import Project, Realty, BuildingClassfication, ConstructionType, Order, Stage, Portfolio, PortfolioPhoto, \ + Answer, AnswerFile class AnswerFileSerializer(ModelSerializer): file = FileField() - + class Meta: model = AnswerFile - + fields = ( 'file', 'id', @@ -26,10 +27,10 @@ class AnswerFileSerializer(ModelSerializer): class PortfolioPhotoSerializer(ModelSerializer): img = ImageField() portfolio_id = PrimaryKeyRelatedField(read_only=True, source='portfolio') - + class Meta: model = PortfolioPhoto - + fields = ( 'id', 'img', @@ -40,7 +41,7 @@ class PortfolioPhotoSerializer(ModelSerializer): class BuildingClassficationSerializer(ModelSerializer): class Meta: model = BuildingClassfication - + fields = ( 'id', 'name', @@ -50,7 +51,7 @@ class BuildingClassficationSerializer(ModelSerializer): class ConstructionTypeSerializer(ModelSerializer): class Meta: model = ConstructionType - + fields = ( 'id', 'name', @@ -62,14 +63,14 @@ class RealtySerializer(ModelSerializer): construction_type = ConstructionTypeSerializer() location = LocationSerializer() user = UserSerializer() - + class Meta: model = Realty - + fields = ( 'id', 'name', - + 'building_classification', 'construction_type', 'location', @@ -78,16 +79,16 @@ class RealtySerializer(ModelSerializer): class StageSerializer(ModelSerializer): - term = serializers.DateField(format="%d.%m.%Y", input_formats=['%d.%m.%Y',]) + term = serializers.DateField(format="%d.%m.%Y", input_formats=['%d.%m.%Y', ]) def validate(self, data): if 'pos' in data and data['pos'] > 1: - pos = data['pos'] -1 + pos = data['pos'] - 1 stage_last = Stage.objects.filter(order=data['order'], pos=pos) if stage_last: stage_last = stage_last[0] if stage_last.term > data['term']: - raise serializers.ValidationError({'term':'Дата не должна быть меньше даты предыдущео этапа'}) + raise serializers.ValidationError({'term': 'Дата не должна быть меньше даты предыдущео этапа'}) return data class Meta: @@ -116,7 +117,7 @@ class OrderSerializer_(ModelSerializer): stages = StageSerializer(many=True) team = TeamSerializer() project_id = PrimaryKeyRelatedField(read_only=True, source='project') - + class Meta: model = Order @@ -125,7 +126,7 @@ class OrderSerializer_(ModelSerializer): 'id', 'secure', 'status', - + 'contractor', 'project_id', 'stages', @@ -136,18 +137,18 @@ 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', @@ -161,8 +162,8 @@ class AnswerSerializer_(ModelSerializer): 'secure_deal_only', 'term', 'term_type', - - 'author', # Generic related field + + 'author', # Generic related field 'content_type', 'files', ) @@ -171,13 +172,13 @@ class AnswerSerializer_(ModelSerializer): class ProjectSerializer(ModelSerializer): answers = AnswerSerializer_(many=True) customer = UserSerializer() - order = OrderSerializer_() # TODO: Can't serialize a reverse/reciprocal relation + order = OrderSerializer_() # TODO: Can't serialize a reverse/reciprocal relation realty = RealtySerializer() specialization = SpecializationSerializer() - + class Meta: model = Project - + fields = ( 'answers', 'budget', @@ -253,10 +254,10 @@ class PortfolioSerializer(ModelSerializer): photos = PortfolioPhotoSerializer(many=True) specialization = SpecializationSerializer() user = UserSerializer() - + class Meta: model = Portfolio - + fields = ( # 'answers', 'building_classification', @@ -265,7 +266,7 @@ class PortfolioSerializer(ModelSerializer): 'photos', 'specialization', 'user', - + 'budget', 'currency', 'description', @@ -282,15 +283,15 @@ class AnswerSerializer(ModelSerializer): files = AnswerFileSerializer(many=True) portfolios = PortfolioSerializer(many=True) project = ProjectSerializer() - + author = GenericRelatedField({ User: UserSerializer(), Team: TeamSerializer() }) - + class Meta: model = Answer - + fields = ( 'budget', 'created', @@ -302,12 +303,12 @@ class AnswerSerializer(ModelSerializer): 'secure_deal_only', 'term', 'term_type', - - 'author', # Generic related field + + 'author', # Generic related field 'content_type', 'files', 'portfolios', 'project', - + # 'candidates', ) diff --git a/projects/signals.py b/projects/signals.py index 9a84217..f03f9bc 100644 --- a/projects/signals.py +++ b/projects/signals.py @@ -1,8 +1,8 @@ -from django.core.signals import request_finished from django.db.models.signals import post_save -from django.utils import timezone from django.dispatch import receiver -from .models import Stage +from django.utils import timezone + +from .models import Stage @receiver(post_save, sender=Stage) diff --git a/projects/templates/_trash/project_form.html b/projects/templates/_trash/project_form.html index 30753ef..bb07f8e 100644 --- a/projects/templates/_trash/project_form.html +++ b/projects/templates/_trash/project_form.html @@ -1,279 +1,279 @@ {% extends 'partials/base.html' %} {% block content %} -
-

Новый заказ

-
+
+

Новый заказ

+
-
- {% csrf_token %} - {{ form_project.errors }} - {{ form_realty.errors }} -
- {{ project_form.errors }} -
-

Формирование заказа

-
-

Название заказа

- - {{ project_form.name.errors }} -
-
-

Подробно опишите задание

- -
-
-
-
-

Дополнительно

-
-
- {# #} - {#
#} - {# #} - {#

+ добавить файл (до 100 файлов)

#} - {#
#} - {# #} - {# #} -
    -
  • - Архитерурное 2.jpg - 7мб -
    -
  • -
-
-

Тип работы:

-
-
- -

Проектирование

-
+
+ {% csrf_token %} + {{ form_project.errors }} + {{ form_realty.errors }} +
+ {{ project_form.errors }} +
+

Формирование заказа

+
+

Название заказа

+ + {{ project_form.name.errors }} +
+
+

Подробно опишите задание

+ +
+
+
+
+

Дополнительно

+
+
+ {# #} + {#
#} + {# #} + {#

+ добавить файл (до 100 файлов)

#} + {#
#} + {# #} + {# #} + {#
    #} + {#
  • #} + {# Архитерурное 2.jpg#} + {# 7мб#} + {#
    #} + {#
  • #} + {#
#} +
+

{% trans 'project_stage0' %}:

+
+
+ +

Проектирование

+
-
- -

Техническое сопровождение

-
-
- -
+
+ +

Техническое сопровождение

+
+
+ +
+
+
+
+
+
Специализация проекта:
+
+
+
-
-
-
-
Специализация проекта:
-
-
-
-
-
-
+
+
- + -{# {{ project_form.specialization }}#} -
-
- -
-
- {# #} -
-
- {# #} -
-
-
-
Бюджет
-
-
-
-
- - -
-
- -
-
- -

или по договоренности

-
-
-
- -

Сделать для исполнителей обязательным для заполнения поля цена и срок

-
-
-
Способ оплаты
-
-
-
- -

- Безопасная сделка (с резервированием бюджета) -

-

- Текст -

-
-
- -

- Прямая оплата Исполнителю на его кошелек/счет -

-

- Текст -

-
-
-
-
-

Расширенный поиск

- -
-
-
-
-
-
-
-
Выбор объекта:
-
Наименование:
-
Классификация здания:
-
Вид строительства:
-
-
-
- -
-
- -
-
- -
-
- -
-
-
-
Местоположение:
-
-
-
- -
-
- -
-
- -

Требуется допуск СРО

-
-
+ {# {{ project_form.specialization }}#} +
+
+ +
+
+ {# #} +
+
+ {# #} +
+
+
+
Бюджет
+
+
+
+
+ + +
+
+ +
+
+ +

или по договоренности

+
+
+
+ +

Сделать для исполнителей обязательным для заполнения поля цена и срок

+
+
+
Способ оплаты
+
+
+
+ +

+ Безопасная сделка (с резервированием бюджета) +

+

+ Текст +

+
+
+ +

+ Прямая оплата Исполнителю на его кошелек/счет +

+

+ Текст +

+
+
+
+
+

Расширенный поиск

+ +
+
+
+
+
+
+
+
Выбор объекта:
+
Наименование:
+
Классификация здания:
+
Вид строительства:
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
Местоположение:
+
+
+
+ +
+
+ +
+
+ +

Требуется допуск СРО

+
+
- - -
-
+
+
+
- + {% endblock %} {% block js_block %} - + }); + }); + {% endblock %} diff --git a/projects/templates/_trash/testport.html b/projects/templates/_trash/testport.html index b21ecc8..ad62e97 100644 --- a/projects/templates/_trash/testport.html +++ b/projects/templates/_trash/testport.html @@ -1,18 +1,18 @@
{% csrf_token %} {{ form.as_p }} -
- Photos - {{ portfolio_photo_form.management_form }} - {{ portfolio_photo_form.non_form_errors }} - {% for form in portfolio_photo_form %} - {{ form.id }} -
- {{ form.img.errors }} - {{ form.img.label_tag }} - {{ form.img }} -
- {% endfor %} -
- +
+ Photos + {{ portfolio_photo_form.management_form }} + {{ portfolio_photo_form.non_form_errors }} + {% for form in portfolio_photo_form %} + {{ form.id }} +
+ {{ form.img.errors }} + {{ form.img.label_tag }} + {{ form.img }} +
+ {% endfor %} +
+
diff --git a/projects/templates/chattest.html b/projects/templates/chattest.html index 2ab5cc3..c6caa23 100644 --- a/projects/templates/chattest.html +++ b/projects/templates/chattest.html @@ -3,7 +3,7 @@

Мои проекты

- {% for proj in request.user.customer_projects.all %} -

{{ proj }}

-
{{ proj.order }}
- {% endfor %} +{% for proj in request.user.customer_projects.all %} +

{{ proj }}

+
{{ proj.order }}
+{% endfor %} diff --git a/projects/templates/comparison.html b/projects/templates/comparison.html index 6a0b4ce..c0fb584 100644 --- a/projects/templates/comparison.html +++ b/projects/templates/comparison.html @@ -4,155 +4,159 @@ {% load i18n %} {% block content %} - {% include 'partials/header.html' %} - -
-
-
-

Сравнение кандидатов по проекту

-
- -
-
-

{{ object }}

- - - - - - - - - - - - - - - - {% for cand in object.candidates.all %} - - - - - - - - - - - - - - - - - - {% endfor %} - -
КандидатЦенаСрокОписаниеРейтинги/отзывыБезопасные сделкиРешение
{{ cand.position }} - {% if cand.answer.author|class_name == 'User' %} - {% firstof cand.answer.author.get_full_name.strip cand.answer.author.username %} - {% elif cand.answer.author|class_name == 'Team' %} - {% firstof cand.answer.author.name.strip cand.answer.author.username %} - {% endif %} - {{ cand.answer.budget }} - {% if cand.answer.term_type == 'project' %} - За проект - {% elif cand.answer.term and cand.answer.term_type %} - {% morph cand.answer.term TERM_TYPE_MORPHS|get:cand.answer.term_type %} - {% endif %} - - - -
    - -
  • {{ cand.answer.author.rating }}
  • - -
  • - +0 0 - 0 -
  • - - {% if cand.answer.author.cro %} -
  • - СРО -
  • - {% endif %} -
-
- 0
- {% if cand.answer.secure_deal_only %} - Готов работать по безопасной сделке - {% endif %} -
-
-
- {% csrf_token %} - - -
-
-
- - -
-
- -
- {% csrf_token %} - -
- - -
-
-
-
-
- - {% include 'partials/footer.html' %} -
+ {% include 'partials/header.html' %} + +
+
+
+

Сравнение кандидатов по проекту

+
+ +
+
+

{{ object }}

+ + + + + + + + + + + + + + + + {% for cand in object.candidates.all %} + + + + + + + + + + + + + + + + + + {% endfor %} + +
КандидатЦенаСрокОписаниеРейтинги/отзывыБезопасные сделкиРешение
{{ cand.position }} + {% if cand.answer.author|class_name == 'User' %} + {% firstof cand.answer.author.get_full_name.strip cand.answer.author.username %} + {% elif cand.answer.author|class_name == 'Team' %} + {% firstof cand.answer.author.name.strip cand.answer.author.username %} + {% endif %} + {{ cand.answer.budget }} + {% if cand.answer.term_type == 'project' %} + За проект + {% elif cand.answer.term and cand.answer.term_type %} + {% morph cand.answer.term TERM_TYPE_MORPHS|get:cand.answer.term_type %} + {% endif %} + + + +
    + +
  • {{ cand.answer.author.rating }}
  • + +
  • + +0 0 + - 0 +
  • + + {% if cand.answer.author.cro %} +
  • + СРО +
  • + {% endif %} +
+
+ 0
+ {% if cand.answer.secure_deal_only %} + Готов работать по безопасной сделке + {% endif %} +
+
+
+ {% csrf_token %} + + +
+
+
+ + +
+
+ +
+ {% csrf_token %} + +
+ + +
+
+
+
+
+ + {% include 'partials/footer.html' %}
+
{% endblock %} {% block js_block %} - + return ui; + }; + $("#compTable tbody").sortable({ + forcePlaceholderSize: true, + forceHelperSize: true, + items: 'tr', + update: function () { + var serial = $('#compTable tbody').sortable('serialize', {key: 'items[]', attribute: 'data-class'}); + console.log(serial); + $.ajax({ + url: '/projects/candidate/comparison/sort/', + method: 'POST', + beforeSend: function (xhr) { + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + }, + data: serial, + dataType: 'json', + success: function (json) { + + console.log(json); + }, + error: function (jqXHR, e) { + console.log(jqXHR); + console.log(e); + } + }); + }, + helper: fixHelper, + + }).disableSelection(); + ; + }); + {% endblock %} diff --git a/projects/templates/contractor_portfolio_edit.html b/projects/templates/contractor_portfolio_edit.html index ecb308d..bb3b989 100644 --- a/projects/templates/contractor_portfolio_edit.html +++ b/projects/templates/contractor_portfolio_edit.html @@ -1,177 +1,184 @@ {% extends 'partials/base.html' %} {% load thumbnail %} - +{% load i18n %} {% block head_css %} - + {% endblock %} {% block content %} - {% include 'partials/header.html' %} + {% include 'partials/header.html' %} -
-
-
-

Изменение портфолио

-
+
+
+
+

Изменение портфолио

+
-
+ {% csrf_token %} - + {{ form.errors }} - +
-

Название {{ form.name.errors.as_text }}

- - +

Название {{ form.name.errors.as_text }}

+ +
-

Описание{{ form.description.errors.as_text }}

- +

Описание{{ form.description.errors.as_text }}

+ +
+ +
+

{% trans 'project_stage0' %} {{ form.work_type.errors.as_text }}

-
-

Тип работы {{ form.work_type.errors.as_text }}

-
-
-
- {{ form.work_type }} -
+
+ {{ form.work_type }} +
-
- -
-
Стадия проекта {{ form.specialization.errors.as_text }}
- -
+
-
-
Стадия проекта
- -
+
+
{% trans 'project_stage1' %} {{ form.specialization.errors.as_text }}
+ +
-
-
Раздел
- -
+
+
{% trans 'project_stage2' %}
+ +
-
-
Подраздел
- -
+
+
{% trans 'project_stage3' %}
+ +
+ +
+
{% trans 'project_stage4' %}
+ +
- +
-

Бюджет{{ form.budget.errors.as_text }}

-
-
- -
-
- {{ form.currency}} -
+

Бюджет{{ form.budget.errors.as_text }}

+
+
+
-
+
+ {{ form.currency }} +
+
+
-

Срок выполнения{{ form.budget.errors.as_text }}

-
-
- -
-
- {{ form.term_type }} -
+

Срок выполнения{{ form.budget.errors.as_text }}

+
+
+ +
+
+ {{ form.term_type }}
-
+
+
-
+
-

Вид строительства

- {{ form.construction_type}} +

Вид строительства

+ {{ form.construction_type }}
-

Классификация здания

- {{ form.building_classification}} +

Классификация здания

+ {{ form.building_classification }}
-
-

Фотографии

- - {% for photo in form.photos.field.queryset.all %} -
-
- × - - {% thumbnail photo.img "200x200" crop="center" as img %} - - {% endthumbnail %} -
- - -
- {% endfor %} - - - -
- -
- - - - Выберите файлы - +

Фотографии

+ + {% for photo in form.photos.field.queryset.all %} +
+
+ × + + {% thumbnail photo.img "200x200" crop="center" as img %} + + {% endthumbnail %} +
+ +
-
+ {% endfor %} + + +
+ + +
- - {% include 'partials/footer.html' %}
+ + + {% include 'partials/footer.html' %}
+
{% endblock %} diff --git a/projects/templates/customer_project_create.html b/projects/templates/customer_project_create.html index 58d54c2..bba6cd8 100644 --- a/projects/templates/customer_project_create.html +++ b/projects/templates/customer_project_create.html @@ -1,346 +1,410 @@ {% extends 'partials/base.html' %} +{% load i18n %} {% block head_css %} - + {% endblock %} {% block content %} - {% include 'partials/modals/project_work_type_suggestion.html' %} - {% include 'partials/header.html' %} + {% include 'partials/modals/project_work_type_suggestion.html' %} + {% include 'partials/header.html' %} -
-
-
-

Новый заказ

+
+
+
+

Новый заказ

+
+ +
+ {% csrf_token %} + + + +
+
+

Формирование заказа

+ + {% if form.non_field_errors %} +
{{ form.non_field_errors }}
+ {% endif %} + +
+

Название заказа + {{ form.name.errors.as_text }}

+ +
+ +
+

Подробно опишите задание + {{ form.text.errors.as_text }}

+ +
+
+
+
+

Дополнительно

+
+
+
+

+ добавить файл (до 100 файлов)

+
+ +
    + +
+
+ +

{% trans 'project_stage0' %} + {{ form.work_type.errors.as_text }}

+
+ {% for id, text in form.work_type.field.choices %} +
+ + +

{{ text }}

+
+ {% endfor %} +
+ +
+
+
+
+
+
+
{% trans 'project_stage1' %} + {{ form.specialization.errors.as_text }}
+ +
+ +
+
{% trans 'project_stage2' %}
+ +
+ +
+
{% trans 'project_stage3' %}
+ +
+ +
+
{% trans 'project_stage4' %}
+ +
+ + +
+
+
Бюджет + {{ form.budget.errors.as_text }}
+ +
+
+
+ + {{ form.currency }} +
+
+ {{ form.term_type }} +
+
+ + +

или по договоренности

+
+
+
+ + +

Сделать для исполнителей обязательным для заполнения поля цена и срок

+
+
+
Способ оплаты {{ form.deal_type.errors.as_text }} +
+
+
+
+ + +

+ Безопасная сделка (с резервированием бюджета) +

+ +

+ {% tooltip pk=10 as tooltip10 %}{{ tooltip10|linebreaksbr }} +

+
+ +
+ + +

+ Прямая оплата Исполнителю на его кошелек/счет +

+ +

+ {% tooltip pk=11 as tooltip11 %}{{ tooltip11|linebreaksbr }} +

+
- - - {% csrf_token %} - - - -
-
-

Формирование заказа

- - {% if form.non_field_errors %} -
{{ form.non_field_errors }}
- {% endif %} - -
-

Название заказа {{ form.name.errors.as_text }}

- -
- -
-

Подробно опишите задание {{ form.text.errors.as_text }}

- -
-
-
-
-

Дополнительно

-
-
-
-

+ добавить файл (до 100 файлов)

-
- -
    - -
-
- -

Тип работы {{ form.work_type.errors.as_text }}

-
- {% for id, text in form.work_type.field.choices %} -
- - -

{{ text }}

-
- {% endfor %} -
- -
+ + +
+
+

Расширенный поиск

+ +
+
+
+
+
+ +
+
+
Выбор объекта
{{ form.realty.errors.as_text }}
+
Наименование
{{ realty_form.name.errors.as_text }}
+
Классификация здания
{{ realty_form.building_classification.errors.as_text }}
+
Вид строительства
{{ realty_form.construction_type.errors.as_text }}
+
+ +
+
+ +
+
+ +
+
+ {{ realty_form.building_classification }} + {# #} + {# {% for c in realty_form.building_classification.field.queryset %}#} + {# #} + {# {% endfor %}#} + {# #} +
+
+
-
-
-
-
-
Стадия проекта {{ form.specialization.errors.as_text }}
- -
- -
-
Стадия проекта
- -
- -
-
Раздел
- -
- -
-
Подраздел
- -
- - -
-
-
Бюджет {{ form.budget.errors.as_text }}
- -
-
-
- - {{ form.currency }} -
-
- {{ form.term_type }} -
-
- - -

или по договоренности

-
-
-
- - -

Сделать для исполнителей обязательным для заполнения поля цена и срок

-
-
-
Способ оплаты {{ form.deal_type.errors.as_text }}
-
-
-
- - -

- Безопасная сделка (с резервированием бюджета) -

- -

- {% tooltip pk=10 as tooltip10 %}{{ tooltip10|linebreaksbr }} -

-
- -
- - -

- Прямая оплата Исполнителю на его кошелек/счет -

- -

- {% tooltip pk=11 as tooltip11 %}{{ tooltip11|linebreaksbr }} -

-
-
- - - - - - -
-
-

Расширенный поиск

- -
-
-
-
-
- -
-
-
Выбор объекта
{{ form.realty.errors.as_text }}
-
Наименование
{{ realty_form.name.errors.as_text }}
-
Классификация здания
{{ realty_form.building_classification.errors.as_text }}
-
Вид строительства
{{ realty_form.construction_type.errors.as_text }}
-
- -
-
- -
-
- -
-
- {{ realty_form.building_classification }} -{# #} -{# {% for c in realty_form.building_classification.field.queryset %}#} -{# #} -{# {% endfor %}#} -{# #} -
-
- -
-
-
-
Местоположение {{ realty_form.location.errors.as_text }}
-
-
-
-
- -
- -
- -
- -
- -
-
- - - -
- -

Требуется допуск (СРО)

-
-
-
- - - -
+
+
+
Местоположение + {{ realty_form.location.errors.as_text }}
+
+
+
+
+ +
+ +
+ +
+ +
+ +
+
+ + + +
+ +

Требуется допуск (СРО)

- - - {% include 'partials/footer.html' %} +
+
+ + + +
+ + + {% include 'partials/footer.html' %}
+
{% endblock %} {% block js_block %} - + }) + }()) + + + + // Scroll to first form validation error --------------------------- + + + ; + (function () { + var hash = $('.-validation-error').filter(function (i, el) { + return $(el).text() + }).first().attr('id') + + if (hash) + window.location.hash = hash + }()) + {% endblock %} diff --git a/projects/templates/customer_project_edit.html b/projects/templates/customer_project_edit.html index 3012ce0..2ec7718 100644 --- a/projects/templates/customer_project_edit.html +++ b/projects/templates/customer_project_edit.html @@ -1,362 +1,397 @@ {% extends 'partials/base.html' %} +{% load i18n %} {% block head_css %} - + {% endblock %} {% block content %} - {% include 'partials/modals/project_work_type_suggestion.html' %} - {% include 'partials/header.html' %} + {% include 'partials/modals/project_work_type_suggestion.html' %} + {% include 'partials/header.html' %} -
-
-
-

Изменение проекта

+
+
+
+

Изменение проекта

+
+ +
+ {% csrf_token %} + + + + +
+
+

Формирование заказа

+ + {% if form.non_field_errors %} +
{{ form.non_field_errors }}
+ {% endif %} + +
+

Название заказа {{ form.name.errors.as_text }}

+ +
+ +
+

Подробно опишите задание {{ form.text.errors.as_text }}

+ +
+
+
+
+

Дополнительно

+
+ +
+
+

+ добавить файл (до 100 файлов)

+
+ +
    + {% for file in form.files.field.queryset.all %} +
  • + +

    {{ file.file.name|basename }} {{ file.file.size|filesizeformat }}

    +
    +
  • + {% endfor %} + + +
+
+ +

{% trans 'project_stage0' %} {{ form.work_type.errors.as_text }} +

+
+ {% for id, text in form.work_type.field.choices %} +
+ + +

{{ text }}

+
+ {% endfor %} +
+ +
+
+
+
+
+
+
{% trans 'project_stage1' %} + {{ form.specialization.errors.as_text }}
+ +
+ +
+
{% trans 'project_stage2' %}
+ +
+ +
+
{% trans 'project_stage3' %}
+ +
+ +
+
{% trans 'project_stage4' %}
+ +
+ + +
+
+
Бюджет {{ form.budget.errors.as_text }}
+ +
+
+
+ + {{ form.currency }} +
+
+ {{ form.term_type }} +
+
+ + +

или по договоренности

+
+
+
+ + +

Сделать для исполнителей обязательным для заполнения поля цена и срок

+
+
+
Способ оплаты {{ form.deal_type.errors.as_text }} +
+
+
+
+ + +

+ Безопасная сделка (с резервированием бюджета) +

+ +

+ {% tooltip pk=10 as tooltip10 %}{{ tooltip10|linebreaksbr }} +

+
+ +
+ + +

+ Прямая оплата Исполнителю на его кошелек/счет +

+ +

+ {% tooltip pk=11 as tooltip11 %}{{ tooltip11|linebreaksbr }} +

+
- - - {% csrf_token %} - - - - -
-
-

Формирование заказа

- - {% if form.non_field_errors %} -
{{ form.non_field_errors }}
- {% endif %} - -
-

Название заказа {{ form.name.errors.as_text }}

- -
- -
-

Подробно опишите задание {{ form.text.errors.as_text }}

- -
-
-
-
-

Дополнительно

-
- -
-
-

+ добавить файл (до 100 файлов)

-
- -
    - {% for file in form.files.field.queryset.all %} -
  • - -

    {{ file.file.name|basename }} {{ file.file.size|filesizeformat }}

    -
    -
  • - {% endfor %} - - -
-
- -

Тип работы {{ form.work_type.errors.as_text }}

-
- {% for id, text in form.work_type.field.choices %} -
- - -

{{ text }}

-
- {% endfor %} -
- -
+ + +
+
+

Расширенный поиск

+ +
+
+
+
+
+
+
+
Выбор объекта
{{ form.realty.errors.as_text }} +
+
Наименование
{{ realty_form.name.errors.as_text }} +
+
Классификация здания
{{ realty_form.building_classification.errors.as_text }} +
+
Вид строительства
{{ realty_form.construction_type.errors.as_text }} +
+
+ +
+
+
-
-
-
-
-
Стадия проекта {{ form.specialization.errors.as_text }}
- -
- -
-
Стадия проекта
- -
- -
-
Раздел
- -
- -
-
Подраздел
- -
- - -
-
-
Бюджет {{ form.budget.errors.as_text }}
- -
-
-
- - {{ form.currency }} -
-
- {{ form.term_type }} -
-
- - -

или по договоренности

-
-
-
- - -

Сделать для исполнителей обязательным для заполнения поля цена и срок

-
-
-
Способ оплаты {{ form.deal_type.errors.as_text }}
-
-
-
- - -

- Безопасная сделка (с резервированием бюджета) -

- -

- {% tooltip pk=10 as tooltip10 %}{{ tooltip10|linebreaksbr }} -

-
- -
- - -

- Прямая оплата Исполнителю на его кошелек/счет -

- -

- {% tooltip pk=11 as tooltip11 %}{{ tooltip11|linebreaksbr }} -

-
-
- - - - - - -
-
-

Расширенный поиск

- -
-
-
-
-
-
-
-
Выбор объекта
{{ form.realty.errors.as_text }}
-
Наименование
{{ realty_form.name.errors.as_text }}
-
Классификация здания
{{ realty_form.building_classification.errors.as_text }}
-
Вид строительства
{{ realty_form.construction_type.errors.as_text }}
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
Местоположение {{ realty_form.location.errors.as_text }}
-
-
-
- -
- -
- -
- -
- -
- - - -
- - -

Требуется допуск СРО

-
-
-
- - -
+
+
- - - {% include 'partials/footer.html' %} +
+ +
+
+ +
+
+
+
Местоположение {{ realty_form.location.errors.as_text }} +
+
+
+
+ +
+ +
+ +
+ +
+ +
+ + + +
+ + +

Требуется допуск СРО

+
+
+
+ + +
+ + + {% include 'partials/footer.html' %}
+
{% endblock %} {% block js_block %} - + }) + }()) + + + + // Scroll to first form validation error ------------------------------ + + + ; + (function () { + var hash = $('.-validation-error').filter(function (i, el) { + return $(el).text() + }).first().attr('id') + + if (hash) + window.location.hash = hash + }()) + {% endblock %} diff --git a/projects/templates/partials/modals/project_work_type_suggestion.html b/projects/templates/partials/modals/project_work_type_suggestion.html index 42ac5cc..bb6f839 100644 --- a/projects/templates/partials/modals/project_work_type_suggestion.html +++ b/projects/templates/partials/modals/project_work_type_suggestion.html @@ -1,55 +1,56 @@