diff --git a/api/urls.py b/api/urls.py index 319beed..eb8ecb1 100755 --- a/api/urls.py +++ b/api/urls.py @@ -10,6 +10,7 @@ from .views import ( StageViewSet, NoteViewSet, DocumentViewSet, + ReviewViewSet, ) @@ -18,6 +19,7 @@ router = routers.DefaultRouter() router.register(r'locations', LocationViewSet) router.register(r'projects', ProjectViewSet) router.register(r'stages', StageViewSet) +router.register(r'reviews', ReviewViewSet) router.register(r'documents', DocumentViewSet) router.register(r'realties', RealtyViewSet) router.register(r'specializations', SpecializationViewSet) diff --git a/api/views.py b/api/views.py index f4e4f4a..09410c3 100755 --- a/api/views.py +++ b/api/views.py @@ -22,6 +22,10 @@ from chat.models import Message, Notes, Documents from chat.serializers import MessageSerializer, NoteSerializer, DocumentsSerializer from chat.filters import MessageFilterSet, NoteFilterSet, DocumentsFilterSet +from reviews.models import Review +from reviews.serializers import ReviewSerializer +from reviews.filters import ReviewFilterSet + class StageViewSet(ModelViewSet): queryset = Stage.objects.all() @@ -30,6 +34,13 @@ class StageViewSet(ModelViewSet): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) +class ReviewViewSet(ModelViewSet): + queryset = Review.objects.all() + serializer_class = ReviewSerializer + filter_class = ReviewFilterSet + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + class DocumentViewSet(ModelViewSet): queryset = Documents.objects.all() serializer_class = DocumentsSerializer diff --git a/archilance/settings/base.py b/archilance/settings/base.py index 9b25660..0600cb5 100644 --- a/archilance/settings/base.py +++ b/archilance/settings/base.py @@ -111,7 +111,7 @@ WSGI_APPLICATION = 'archilance.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'archilance', + 'NAME': 'archilance2', 'USER': 'postgres', 'PASSWORD': 'postgres', 'HOST': 'localhost', diff --git a/archilance/urls.py b/archilance/urls.py index 373a2a1..a7990f2 100644 --- a/archilance/urls.py +++ b/archilance/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ url(r'^work_sell/', include('work_sell.urls')), url(r'^test/$', TemplateView.as_view(template_name='test.html'), 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')), diff --git a/chat/templates/chat_contractor.html b/chat/templates/chat_contractor.html index a452670..35e5215 100644 --- a/chat/templates/chat_contractor.html +++ b/chat/templates/chat_contractor.html @@ -157,7 +157,7 @@ - сохранить + сохранить @@ -195,7 +195,7 @@ {% endif %} {% endfor %}

- + Полное описание заказа @@ -371,9 +371,28 @@ .parent().addClass($.support.fileInput ? undefined : 'disabled'); - $("#approve-stages").on('click', function(){ + $("#order-stages").on('click',"#approve-stages",function(e){ + e.preventDefault(); $(".stage-block-approve").each(function(){ var stageId = $(this).attr('data-id'); + + $.ajax({ + url: '/api/stages/' + stageId + '/', + type: 'PATCH', + beforeSend: function(xhr){ + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')) + }, + data: "status=in_process", + dataType:'json', + success: function(json){ + console.log(json); + }, + error: function(e){ + console.log('error'); + console.log(e); + } + }); + }); }); @@ -496,7 +515,7 @@ '

Результаты этапа:' + v.result + '

' + '

'+ v.status+'

' + v.cost + '
'; }); - htmlInbox += '
согласовать
'; + htmlInbox += '
согласовать
'; } $("#order-stages").html(htmlInbox); diff --git a/chat/templates/chat_customer.html b/chat/templates/chat_customer.html index 5f8f066..865a822 100644 --- a/chat/templates/chat_customer.html +++ b/chat/templates/chat_customer.html @@ -24,7 +24,7 @@
- +
@@ -79,9 +79,9 @@
- + - +
@@ -136,79 +136,51 @@

-{#
#} -{#

#} -{# Какое кол-во этапов подразумевает работа? #} -{#

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

2 / Резервирование

-

- Резервирование заказчиком суммы оплаты по заказ. Деньги перечисляются и хранятся на сайте. -

+

2 / Резервирование

+

+ Резервирование заказчиком суммы оплаты по заказ. Деньги перечисляются и хранятся на + сайте. +

+ Зарезирвировать +
-
+
-
@@ -238,46 +268,46 @@ var form = document.getElementById('message_form'); var csrftoken = getCookie('csrftoken'); - function getStages(orderId){ + function getStages(orderId) { - $.ajax({ + $.ajax({ url: '/api/stages/', type: 'GET', data: {csrfmiddlewaretoken: csrftoken, 'order': orderId}, dataType: 'json', success: function (json) { var stageCount = json.results.length; - if (stageCount == 0){ + if (stageCount == 0) { stageCountVal = 1; - }else{ + } else { stageCountVal = stageCount; } var htmlInbox = ""; var htmlInboxStage = '

Какое кол-во этапов подразумевает работа? ' + - '

'; - - if (stageCount == 0){ - htmlInboxStage += '
' + - '

Этап 1

' + - '' + - '' + - ' />' + - '' + - '' + - '
'; + '

'; + + if (stageCount == 0) { + htmlInboxStage += '
' + + '

Этап 1

' + + '' + + '' + + ' />' + + '' + + '' + + '
'; } var statusNotAgreed = true; $.each(json.results, function (i, v) { if (v.status == "not_agreed") { htmlInbox += '
' + - '

Этап

' + - '' + - '' + - '' + - '' + - '' + + '

Этап

' + + '' + + '' + + '' + + '' + + '' + '
'; } else { statusNotAgreed = false; @@ -299,55 +329,55 @@ $("#order-stages").html(htmlInbox); } }); - } - setTimeout(function(){ - $(".user-block").first().trigger('click'); + } + + setTimeout(function () { + $(".user-block").first().trigger('click'); }, 10); - setTimeout(function(){ - $(".order-block").first().trigger('click'); + setTimeout(function () { + $(".order-block").first().trigger('click'); }, 100); - $("#order-stages").on('click', "#addStagesForm", function(e){ + $("#order-stages").on('click', "#addStagesForm", function (e) { e.preventDefault(); - $(".new-stages-form").each(function(i,v){ - console.log($(this).serialize()); + $(".new-stages-form").each(function (i, v) { + console.log($(this).serialize()); - $.ajax({ + $.ajax({ url: '/api/stages/', type: 'POST', beforeSend: function (xhr) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')) }, - data:$(this).serialize(), + data: $(this).serialize(), dataType: 'json', success: function (json) { console.log(json); }, - error: function(e){ + error: function (e) { console.log('error'); console.log(e); } }); - }); + }); - $(".update-stages-form").each(function(i,v){ - var currentStageId = parseInt($(this).attr('data-order-id')); - alert(currentStageId); + $(".update-stages-form").each(function (i, v) { + var currentStageId = parseInt($(this).attr('data-stage-id')); $.ajax({ url: '/api/stages/' + currentStageId + '/', type: 'PUT', - beforeSend: function(xhr){ + beforeSend: function (xhr) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')) }, data: $(this).serialize(), - dataType:'json', - success: function(json){ + dataType: 'json', + success: function (json) { console.log(json); }, - error: function(e){ + error: function (e) { console.log('error'); console.log(e); } @@ -355,8 +385,6 @@ }); - - var currentOrderId = $(this).attr('data-order-id'); currentOrderId = parseInt(currentOrderId); getStages(currentOrderId); @@ -364,7 +392,7 @@ }); - $('#order-stages-tab').on('change', '#countStage', function(){ + $('#order-stages-tab').on('change', '#countStage', function () { var countStage = parseInt($(this).val()); var updateFormStages = $(".update-stages-form"); var limitCount = countStage + 1; @@ -376,7 +404,7 @@ $('.order-block').on('click', function () { $("#chat-order-add").css("display", "block"); - $("#formsetStage").css("display","block"); + $("#formsetStage").css("display", "block"); $('.order-block').each(function () { $(this).removeClass('orAct'); @@ -386,6 +414,8 @@ var orderId = $(this).attr('data-id'); var recipentId = $(this).attr('data-recipent-id'); $("#chat-order-add #orderId").val(orderId); + $("#projectReviewId").val(orderId); + $("#targetContractorId").val(recipentId); $("#chat-order-add #recipentId").val(recipentId); $(".orderStagesInput").val(orderId); var inbox = document.getElementById('message-chat-order-space'); @@ -411,70 +441,7 @@ }); } }); - getStages(orderId); - -{# $.ajax({#} -{# url: '/api/stages/',#} -{# type: 'GET',#} -{# data: {csrfmiddlewaretoken: csrftoken, 'order': orderId},#} -{# dataType: 'json',#} -{# success: function (json) {#} -{# var stageCount = json.results.length;#} -{# if (stageCount == 0){#} -{# stageCountVal = 1;#} -{# }else{#} -{# stageCountVal = stageCount;#} -{# }#} -{##} -{# var htmlInbox = "";#} -{##} -{# var htmlInboxStage = '

Какое кол-во этапов подразумевает работа? ' +#} -{# '

';#} -{##} -{# if (stageCount == 0){#} -{# htmlInboxStage += '
' +#} -{# '

Этап 1

' +#} -{# '' +#} -{# '' +#} -{# ' />' +#} -{# '' +#} -{# '' +#} -{# '
';#} -{# }#} -{# var statusNotAgreed = true;#} -{# $.each(json.results, function (i, v) {#} -{# if (v.status == "not_agreed") {#} -{# htmlInbox += '
' +#} -{# '

Этап

' +#} -{# '' +#} -{# '' +#} -{# '' +#} -{# '' +#} -{# '' +#} -{# '
';#} -{# } else {#} -{# statusNotAgreed = false;#} -{# htmlInboxStage = "";#} -{# htmlInbox += '
' +#} -{# '

Этап ' + v.pos + '' + v.name + '

' +#} -{# '

Результаты этапа:' + v.result + '

' +#} -{# '

до 16.03.2015

' + v.cost + '
';#} -{##} -{# }#} -{# });#} -{##} -{# if (statusNotAgreed) {#} -{# htmlInbox += '
Перейти в режим безопасной сделки' +#} -{# '
' +#} -{# 'отправить на согласование
';#} -{# }#} -{# htmlInbox = htmlInboxStage + htmlInbox;#} -{# $("#order-stages").html(htmlInbox);#} -{# }#} -{# });#} - - }); // Вытащить сообщения для конактов @@ -527,6 +494,10 @@ $("#chat").val(""); }); + $('#order-review-add').on('click', function(){ + alert('add review'); + }); + $('#order-chat-add-message').on('click', function () { var chatMessage = $("#chat-order-add #chat").val(); var recipentId = $("#chat-order-add #recipentId").val(); @@ -550,9 +521,6 @@ }); - - - var userId = '{{ request.user.pk }}'; var SocketHandler = function () { var url = 'ws://127.0.0.1:8888/chat/' + userId + '/'; diff --git a/chat/testapp.py b/chat/testapp.py index fe64028..0922d45 100644 --- a/chat/testapp.py +++ b/chat/testapp.py @@ -92,7 +92,7 @@ if __name__ == '__main__': ioloop = IOLoop.instance() application.db = momoko.Pool( - dsn='dbname=archilance user=postgres password=postgres host=localhost', + dsn='dbname=archilance2 user=postgres password=postgres host=localhost', size=1, ioloop=ioloop, ) diff --git a/chat/views.py b/chat/views.py index 6484f04..5edab5e 100644 --- a/chat/views.py +++ b/chat/views.py @@ -5,6 +5,7 @@ from django.forms import formset_factory from .models import Message from .forms import ArticleForm +from reviews.forms import Review from users.models import User diff --git a/reviews/filters.py b/reviews/filters.py new file mode 100644 index 0000000..db9119c --- /dev/null +++ b/reviews/filters.py @@ -0,0 +1,18 @@ +from rest_framework_filters import FilterSet, RelatedFilter, AllLookupsFilter + +from .models import Review + + +class ReviewFilterSet(FilterSet): + text = AllLookupsFilter() + type = AllLookupsFilter() + created = AllLookupsFilter() + from_customer = RelatedFilter('users.filters.UserFilterSet') + from_contractor = RelatedFilter('users.filters.UserFilterSet') + target_customer = RelatedFilter('users.filters.UserFilterSet') + target_contractor = RelatedFilter('users.filters.UserFilterSet') + + class Meta: + model = Review + + diff --git a/reviews/forms.py b/reviews/forms.py new file mode 100644 index 0000000..545ab92 --- /dev/null +++ b/reviews/forms.py @@ -0,0 +1,21 @@ +from django import forms +from .models import Review + + +class ReviewForm(forms.ModelForm): + + class Meta: + model = Review + + fields = ( + 'type', + 'text', + 'project', + 'from_customer', + 'target_contractor', + 'target_team', + 'from_contractor', + 'target_customer', + 'from_team', + ) + diff --git a/reviews/migrations/0003_auto_20160811_1113.py b/reviews/migrations/0003_auto_20160811_1113.py new file mode 100644 index 0000000..efe6267 --- /dev/null +++ b/reviews/migrations/0003_auto_20160811_1113.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-11 08:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0007_auto_20160808_1557'), + ('reviews', '0002_remove_review_is_secured'), + ] + + operations = [ + migrations.AddField( + model_name='review', + name='from_team', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='team_reviews', to='users.Team'), + ), + migrations.AddField( + model_name='review', + name='target_team', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='reviews_by_team', to='users.Team'), + ), + migrations.AlterUniqueTogether( + name='review', + unique_together=set([('from_customer', 'target_contractor', 'project'), ('from_contractor', 'target_customer', 'project'), ('from_team', 'target_customer', 'project'), ('from_customer', 'target_team', 'project')]), + ), + ] diff --git a/reviews/migrations/0004_auto_20160811_1507.py b/reviews/migrations/0004_auto_20160811_1507.py new file mode 100644 index 0000000..2b7948c --- /dev/null +++ b/reviews/migrations/0004_auto_20160811_1507.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-08-11 12:07 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('reviews', '0003_auto_20160811_1113'), + ] + + operations = [ + migrations.RemoveField( + model_name='review', + name='stars', + ), + migrations.AddField( + model_name='review', + name='created', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name='review', + name='type', + field=models.CharField(choices=[('positive', 'Положительный'), ('negative', 'Отрицательный'), ('neutral', 'Нейтральный')], default='neutral', max_length=30), + ), + ] diff --git a/reviews/models.py b/reviews/models.py index c51ee6e..174e2c3 100644 --- a/reviews/models.py +++ b/reviews/models.py @@ -1,23 +1,55 @@ from django.db import models -from django.core.validators import MaxValueValidator, MinValueValidator +from django.core.urlresolvers import reverse +from django.utils import timezone + +TYPE_REVIEWS = ( + ('positive', 'Положительный'), + ('negative', 'Отрицательный'), + ('neutral', 'Нейтральный'), +) + class Review(models.Model): project = models.ForeignKey('projects.Project', related_name='reviews') - stars = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)]) + type = models.CharField(max_length=30, choices=TYPE_REVIEWS, default='neutral') text = models.TextField() + created = models.DateTimeField(default=timezone.now) target_customer = models.ForeignKey('users.User', related_name='reviews_by_contractor', null=True, blank=True) target_contractor = models.ForeignKey('users.User', related_name='reviews_by_customer', null=True, blank=True) + target_team = models.ForeignKey('users.Team', related_name='reviews_by_team', null=True, blank=True) from_customer = models.ForeignKey('users.User', related_name='contractor_reviews', null=True, blank=True) from_contractor = models.ForeignKey('users.User', related_name='customer_reviews', null=True, blank=True) + from_team = models.ForeignKey('users.Team', related_name='team_reviews', null=True, blank=True) def __str__(self): return str(self.pk) - + + def get_absolute_url(self): + return reverse('reviews:detail', kwargs={'pk': self.pk}) + + def get_sender(self): + if not self.from_team is None: + return self.from_team.name + elif not self.from_customer is None: + return self.from_customer.username + else: + return self.from_contractor.username + + def get_recipient(self): + if not self.target_team is None: + return self.target_team.name + elif not self.target_customer is None: + return self.target_customer.username + else: + return self.target_contractor.username + class Meta: verbose_name = 'Отзыв' verbose_name_plural = 'Отзывы' unique_together = ( ('from_customer', 'target_contractor', 'project'), + ('from_customer', 'target_team', 'project'), ('from_contractor', 'target_customer', 'project'), + ('from_team', 'target_customer', 'project'), ) diff --git a/reviews/serializers.py b/reviews/serializers.py new file mode 100644 index 0000000..edaa702 --- /dev/null +++ b/reviews/serializers.py @@ -0,0 +1,24 @@ +from rest_framework.serializers import ModelSerializer + +from .models import Review +from users.serializers import UserSerializer, TeamSerializer + + +class ReviewSerializer(ModelSerializer): + + class Meta: + model = Review + + fields = ( + 'text', + 'type', + 'created', + 'project', + 'from_customer', + 'from_contractor', + 'from_team', + 'target_customer', + 'target_contractor', + 'target_team', + ) + diff --git a/reviews/templates/review_create.html b/reviews/templates/review_create.html new file mode 100644 index 0000000..67f7600 --- /dev/null +++ b/reviews/templates/review_create.html @@ -0,0 +1,21 @@ +

Добавление отзыва

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

+

+ {% for id, name in form.type.field.choices %} + + {{ name }} + + {% endfor %} +
+

+

+ {{ form.text }} + +

+ + +
diff --git a/reviews/templates/reviews_list.html b/reviews/templates/reviews_list.html new file mode 100644 index 0000000..0fdbd7c --- /dev/null +++ b/reviews/templates/reviews_list.html @@ -0,0 +1,41 @@ +{% extends 'partials/base.html' %} + +{% load staticfiles %} +{% load thumbnail %} +{% block content %} + + +{% include 'partials/header.html' %} +
+
+
+

Все отзывы

+
+ +
+ {% for review in object_list %} +
+
+

+ {{ review.get_sender }} +

+ {% if review.project.deal_type == 'secure_deal' %} + + Безопасная сделка + + {% endif %} + +

+ {{ review.text }} +

+
+
+ {% endfor %} +
+ + {% include 'partials/footer.html' %} +
+
+{% endblock %} diff --git a/reviews/urls.py b/reviews/urls.py new file mode 100644 index 0000000..2c64798 --- /dev/null +++ b/reviews/urls.py @@ -0,0 +1,11 @@ +from django.conf import urls + +from .views import ReviewsView, ReviewCreateView + +app_name = 'reviews' + +urlpatterns = [ + urls.url(r'^$', ReviewsView.as_view(), name='list'), + urls.url(r'^create/$', ReviewCreateView.as_view(), name='create'), + +] diff --git a/reviews/views.py b/reviews/views.py index 91ea44a..a747508 100644 --- a/reviews/views.py +++ b/reviews/views.py @@ -1,3 +1,21 @@ from django.shortcuts import render +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.views.generic import ListView, CreateView, DeleteView +from .models import Review +from .forms import ReviewForm -# Create your views here. + +class ReviewsView(LoginRequiredMixin, ListView): + login_url = '/users/login/' + model = Review + template_name = 'reviews_list.html' + + +class ReviewCreateView(CreateView): + model = Review + form_class = ReviewForm + template_name = 'review_create.html' + + +class ReviewDeleteView(DeleteView): + model = Review diff --git a/users/templates/contractor_profile.html b/users/templates/contractor_profile.html index 38d52e8..45760a1 100644 --- a/users/templates/contractor_profile.html +++ b/users/templates/contractor_profile.html @@ -402,28 +402,28 @@
-
+ + {% for review in contractor.contractor_reviews.all %} +

- Иванов Петр Иванович + {{ review.get_sender }}

+ {% if review.project.deal_type == 'secure_deal' %} Безопасная сделка + {% endif %}

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean euismod bibendum - + {{ review.text }}

-
+
+ {% endfor %} +
diff --git a/wallets/__init__.py b/wallets/__init__.py index e69de29..9ad88f2 100644 --- a/wallets/__init__.py +++ b/wallets/__init__.py @@ -0,0 +1 @@ +default_app_config = 'wallets.apps.WalletsConfig' diff --git a/wallets/apps.py b/wallets/apps.py index 97938d4..9d3ed4d 100644 --- a/wallets/apps.py +++ b/wallets/apps.py @@ -3,3 +3,6 @@ from django.apps import AppConfig class WalletsConfig(AppConfig): name = 'wallets' + + def ready(self): + import wallets.signals diff --git a/wallets/signals.py b/wallets/signals.py index 3afa521..ec3b071 100644 --- a/wallets/signals.py +++ b/wallets/signals.py @@ -3,7 +3,7 @@ from django.dispatch import receiver from django.core.mail import send_mail, EmailMultiAlternatives from django.template.loader import get_template, render_to_string -from .models import WithDraw +from .models import WithDraw, InvoiceHistory @receiver(post_save, sender=WithDraw) @@ -18,3 +18,5 @@ def send_for_accountant(sender, instance, created, **kwargs): html_content = get_template('send_for_accountant.html').render(ctx_dict) msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) msg.send() + +