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/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 @@
-
+
-
+
-
+
@@ -136,79 +136,51 @@
-{#
#}
-{#
#}
-{# Какое кол-во этапов подразумевает работа? #}
-{#
#}
-{#
#}
-{##}
-{#
#}
-{#
#}
-
-
2 / Резервирование
-
- Резервирование заказчиком суммы оплаты по заказ. Деньги перечисляются и хранятся на сайте.
-
+
2 / Резервирование
+
+ Резервирование заказчиком суммы оплаты по заказ. Деньги перечисляются и хранятся на
+ сайте.
+
+
Зарезирвировать
+
-
+
-
-
-
-
+
+
-
-
3 / Выполнение работы
-
- Процесс выполнения задания в заказе до получения заказчиком итогового результата работы.
-
-
+
+
3 / Выполнение работы
+
+ Процесс выполнения задания в заказе до получения заказчиком итогового результата работы.
+
+
@@ -216,13 +188,71 @@
сохранить
-
-
+
+
+
+
+
{% include 'partials/footer.html' %}
@@ -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 += '';
+ '';
+
+ if (stageCount == 0) {
+ htmlInboxStage += '';
}
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 += '';#}
-{# }#}
-{# 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/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 @@
+Добавление отзыва
+
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()
+
+