remotes/origin/PR-39
shishman 10 years ago
commit a1d15976fe
  1. 4
      api/urls.py
  2. 1
      api/views.py
  3. 2
      archilance/management/commands/generate_projects.py
  4. 10
      archilance/util.py
  5. 165
      assets/js/chat.js
  6. 22
      chat/migrations/0007_auto_20160826_1458.py
  7. 2
      chat/models.py
  8. 19
      chat/response.py
  9. 33
      chat/templates/arbitration_modal.html
  10. 91
      chat/templates/chat_contractor.html
  11. 121
      chat/templates/chat_customer.html
  12. 16
      chat/templates/review_add_modal.html
  13. 2
      chat/urls.py
  14. 15
      chat/utils.py
  15. 26
      chat/views.py
  16. 8
      projects/admin.py
  17. 2
      projects/forms.py
  18. 33
      projects/migrations/0017_arbitration.py
  19. 1
      projects/mixins.py
  20. 31
      projects/models.py
  21. 2
      projects/serializers.py
  22. 6
      projects/templates/project_detail.html
  23. 2
      projects/urls.py
  24. 40
      projects/views.py
  25. 3
      ratings/templatetags/specializtions_tags.py
  26. 1
      reviews/__init__.py
  27. 5
      reviews/apps.py
  28. 31
      reviews/signals.py
  29. 18
      templates/partials/footer.html
  30. 10
      users/models.py
  31. 2
      users/templates/contractor_filter.html
  32. 30
      users/templates/contractor_office.html
  33. 38
      users/views.py
  34. 19
      work_sell/migrations/0006_remove_worksell_team.py
  35. 9
      work_sell/models.py

@ -8,6 +8,7 @@ from .views import (
LocationViewSet,
MessageViewSet,
NoteViewSet,
OrderViewSet,
PortfolioPhotoViewSet,
PortfolioViewSet,
ProjectViewSet,
@ -17,7 +18,6 @@ from .views import (
StageViewSet,
TeamViewSet,
UserViewSet,
OrderViewSet,
)
@ -30,10 +30,10 @@ router.register(r'documents', DocumentViewSet)
router.register(r'locations', LocationViewSet)
router.register(r'message', MessageViewSet)
router.register(r'note', NoteViewSet)
router.register(r'orders', OrderViewSet)
router.register(r'portfolio-photos', PortfolioPhotoViewSet)
router.register(r'portfolios', PortfolioViewSet)
router.register(r'projects', ProjectViewSet)
router.register(r'orders', OrderViewSet)
router.register(r'realties', RealtyViewSet)
router.register(r'reviews', ReviewViewSet)
router.register(r'specializations', SpecializationViewSet)

@ -115,6 +115,7 @@ class OrderViewSet(ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
filter_class = OrderFilterSet
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class SpecializationViewSet(ModelViewSet):

@ -78,7 +78,7 @@ class Command(BaseCommand):
project=project,
contractor=_.sample((None, User.contractor_objects.order_by('?').first())),
secure=_.sample((True, False)),
status=_.sample((True, False)),
status=_.sample(Order.STATUSES)[0],
)
return project

@ -1,4 +1,5 @@
from django.core import validators
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import _get_queryset
from django.utils import timezone
from pprint import pprint, pformat
@ -54,6 +55,15 @@ def get_or_none(klass, *args, **kwargs):
return None
def get_related_or_none(obj, attr):
try:
rel = getattr(obj, attr)
except ObjectDoesNotExist:
rel = None
return rel
def get_attr_or_none(klass, *args, attr=None, **kwargs):
object = get_or_none(klass, *args, **kwargs)

@ -21,7 +21,7 @@ var SocketHandler = function () {
inbox = document.getElementById('message-chat-team-space');
} else if (message.answer_type == 'approve_stages') {
var resOrderId = message.order_id;
$.jGrowl(message.msg,{
$.jGrowl(message.msg, {
life: 4000
});
setTimeout(function () {
@ -34,12 +34,12 @@ var SocketHandler = function () {
var classMessage = 'youChat';
var senderName = 'Вы';
var timeMessage = message.msg_time;
if (message.sender_id != userId){
if (message.sender_id != userId) {
senderName = message.sender_name;
classMessage = '';
}
inbox.innerHTML += '<div class="col-lg-12 insetCommChat '+ classMessage +'"><div class="topCommChat">' +
'<p class="nameCommChat">'+ senderName +'</p> <span>' + timeMessage + '</span></div>' +
inbox.innerHTML += '<div class="col-lg-12 insetCommChat ' + classMessage + '"><div class="topCommChat">' +
'<p class="nameCommChat">' + senderName + '</p> <span>' + timeMessage + '</span></div>' +
'<p class="textCommChat">' + textMessage + '</p></div>';
}
@ -98,17 +98,32 @@ function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
var socket = new SocketHandler();
var csrftoken = getCookie('csrftoken');
$(function () {
setTimeout(function () {
$(".user-block").first().trigger('click');
}, 10);
var currentHash = URI(location.href).hash();
console.log(currentHash);
if (currentHash.indexOf("#order") == 0) {
setTimeout(function () {
$(".order-block").last().trigger('click');
$("a[href='#tab2']").trigger('click');
}, 100);
var ordHashId = currentHash.replace("#order", "");
setTimeout(function () {
$("#orderBlock" + ordHashId).trigger('click');
$("a[href='#tab2']").trigger('click');
}, 100);
} else if (currentHash.indexOf("#team") == 0) {
} else {
setTimeout(function () {
$(".user-block").first().trigger('click');
}, 10);
setTimeout(function () {
$(".order-block").last().trigger('click');
}, 100);
}
$('.deleteMess').on('click', function (e) {
@ -123,10 +138,10 @@ $(function () {
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))
},
data: {'sender_id':senderId, 'recipent_id': recipentId},
data: {'sender_id': senderId, 'recipent_id': recipentId},
dataType: 'json',
success: function (json) {
if (json.status == 'ok'){
if (json.status == 'ok') {
_this.parent().remove();
$("#message-chat-space").html("");
}
@ -161,6 +176,130 @@ $(function () {
}
});
});
// Добавление сообщения для заказа.
$('#order-chat-add-message').on('click', function (e) {
e.preventDefault();
var chatMessage = $("#chat-order-add #chat").val();
var recipentId = $("#chat-order-add #recipentId").val();
var senderId = $("#chat-order-add #senderId").val();
var orderId = $("#chat-order-add #orderId").val();
socket.add_contact_message({
"format_type": "add_message_order",
"data": {
"sender_id": senderId,
"recipent_id": recipentId,
"chat_message": chatMessage,
"order_id": orderId,
}
});
$("#chat-order-add #chat").val("");
$("#document-send-order").html("");
});
// Добавление отзыва
$('#order-review-add').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
var formData = $("#review-adds-form").serialize();
$.ajax({
url: '/api/reviews/',
type: 'POST',
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))
},
data: formData,
dataType: 'json',
success: function (json) {
console.log("Успешно");
$("#review-add").modal('hide');
$.jGrowl("Ваш отзыв успешно добавлен", {
life: 4000
});
},
error: function (e) {
console.log('error');
console.log(e);
}
});
});
// Добавление сообщения в арбитраж
$('#order-arbitration-add').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
var formData = $("#arbitration-add-form").serialize();
$.ajax({
url: '/projects/arbitration/create/',
type: 'POST',
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))
},
data: formData,
dataType: 'json',
success: function (json) {
console.log(json);
$("#arbitration-add").modal('hide');
$.jGrowl("Обращение в арбитраж добавлено", {
life: 4000
});
},
error: function (e) {
console.log('error');
console.log(e);
}
});
});
//Загрузка документов
$('#upload-document-order').fileupload({
url: '/chat/create/',
formData: {
sender: $("#chat-order-add #senderId").val(),
recipent: $("#chat-order-add #recipentId").val(),
order: $("#chat-order-add #orderId").val(),
},
crossDomain: false,
beforeSend: function (xhr, settings) {
$('#progress .progress-bar').css(
'width',
'0%'
);
if (!csrfSafeMethod(settings.type)) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
dataType: 'json',
done: function (e, data) {
$.each(data.result.files, function (index, file) {
var currentValue = '';
currentValue += file.id + ';';
//$("#documentSendIds").val(currentValue);
var htmlImg = '<p>' + file.name + '</p>';
var document_send = $(htmlImg).appendTo("#document-send-order");
});
},
fail: function (e) {
console.log(e);
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
'width',
progress + '%'
);
}
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
});

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-26 11:58
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('chat', '0006_message_is_delete'),
]
operations = [
migrations.AlterField(
model_name='documents',
name='recipent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='recipent_documents', to=settings.AUTH_USER_MODEL),
),
]

@ -44,7 +44,7 @@ class Documents(models.Model):
order = models.ForeignKey(Order, related_name='documents', null=True, blank=True)
team = models.ForeignKey(Team, related_name='documents', null=True, blank=True)
sender = models.ForeignKey(User, related_name='sender_documents')
recipent = models.ForeignKey(User, related_name='recipent_documents')
recipent = models.ForeignKey(User, related_name='recipent_documents', null=True, blank=True)
def __str__(self):
return self.file.url

@ -0,0 +1,19 @@
import json
from django.http import HttpResponse
MIMEANY = '*/*'
MIMEJSON = 'application/json'
MIMETEXT = 'text/plain'
def response_mimetype(request):
can_json = MIMEJSON in request.META['HTTP_ACCEPT']
can_json |= MIMEANY in request.META['HTTP_ACCEPT']
return MIMEJSON if can_json else MIMETEXT
class JSONResponse(HttpResponse):
def __init__(self, obj='', json_opts=None, mimetype=MIMEJSON, *args, **kwargs):
json_opts = json_opts if isinstance(json_opts, dict) else {}
content = json.dumps(obj, **json_opts)
super().__init__(content, mimetype, *args, **kwargs)

@ -0,0 +1,33 @@
<div id="arbitration-add" class="modal fade" role="dialog">
<div class="modal-dialog" role="document" style="width:900px;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×
</button>
<h4 class="modal-title">Оставить заявку в арбитраж</h4>
</div>
<form id="arbitration-add-form" method="POST">
<div class="modal-body">
<div style="height: 250px;">
<div class="textAreaBlock2 text-nn box-sizing disTab">
<p>Ваш текст</p>
<textarea id="text-new" name="text"></textarea>
<input type="hidden" name="order" id="orderArbitrationId" >
<input type="hidden" name="user" value="{{ request.user.pk }}">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Закрыть
</button>
<button type="button" id="order-arbitration-add" class="btn btn-primary">Отправить
</button>
</div>
</form>
</div>
</div>
</div>

@ -107,7 +107,7 @@
<div class="messageBlock box-sizing disTab">
<p>Заказы</p>
{% for order in orders %}
<div class="orderBlock box-sizing order-block"
<div class="orderBlock box-sizing order-block" data-project-id="{{ order.project.id }}"
id="orderBlock{{ order.id }}" data-recipent-id="{{ order.project.customer.pk }}" data-id="{{ order.id }}">
<span class="dimovChat"></span>
<p class="titleOB">{{ order }}</p>
@ -126,18 +126,19 @@
</div>
<div class="col-lg-6 commChat">
<div id="message-chat-order-space"></div>
<form id="chat-contractor-order">
<input type="hidden" id="orderId"/>
<input type="hidden" id="senderOrderId" value="{{ request.user.pk }}"/>
<input type="hidden" id="recipentOrderId"/>
<form id="chat-order-add">
<input type="hidden" id="orderId">
<input type="hidden" id="senderId" value="{{ request.user.pk }}"/>
<input type="hidden" id="recipentId">
<textarea id="chat" class="box-sizing"></textarea>
<div class="bunChat">
<div class="setChat box-sizing">
<p>Прикрепить файл</p>
<span>Не более 10 файлов с общим объемом 500мб</span>
<div class="setChat box-sizing upload">
<input type="file" name="file" id="upload-document-order"/>
<p>Прикрепить файл</p>
</div>
<a href="javascript:void(0)" id="order-chat-add-message">отправить</a>
<div id="document-send-order"></div>
<a href="#" id="order-chat-add-message">отправить</a>
</div>
</form>
@ -181,6 +182,13 @@
</a>
</div>
<!-- Review add -->
{% include 'review_add_modal.html' %}
<!-- -->
<!-- Arbitration add -->
{% include 'arbitration_modal.html' %}
<!-- -->
<div class="textAreaBlock2 box-sizing disTab">
<ul class="notes-block">
@ -260,10 +268,8 @@
<div class="progress-bar progress-bar-success"></div>
</div>
<div id="document-send">
</div>
<a href="javascript:void(0)" id="add-team-chat-message">отправить</a>
<div id="document-send"></div>
<a href="#" id="add-team-chat-message">отправить</a>
</div>
</form>
</div>
@ -291,19 +297,22 @@
<script type="text/javascript">
$(function () {
var currentChatUser = {{ request.user.pk }};
var socket = new SocketHandler();
var form = document.getElementById('message_form');
var csrftoken = getCookie('csrftoken');
setTimeout(function () {
$(".team-order-block").first().trigger('click');
}, 1000);
var url = '/work_sell/basic/';
var url = '/chat/create/';
//Загрузка документов
$('#upload-document-team').fileupload({
url: url,
formData: {
sender: $("#team-chat-form #senderId").val(),
recipent: $("#team-chat-form #recipentId").val(),
team: $("#team-chat-form #teamId").val(),
order: $("#team-chat-form #orderId").val(),
},
crossDomain: false,
beforeSend: function (xhr, settings) {
$('#progress .progress-bar').css(
@ -324,6 +333,9 @@
var document_send = $(htmlImg).appendTo("#document-send");
});
},
fail: function(e){
console.log(e);
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
@ -424,12 +436,19 @@
var orderId = $(this).attr('data-id');
var recipentId = $(this).attr('data-recipent-id');
$("#chat-contractor-order #orderId").val(orderId);
var projectId = $(this).attr('data-project-id');
$("#chat-order-add #orderId").val(orderId);
$("#add-form-order-note #orderNote").val(orderId);
$("#orderArbitrationId").val(orderId);
$("#projectReviewId").val(projectId);
$("#chat-contractor-order #recipentOrderId").val(recipentId);
$("#chat-order-add #recipentId").val(recipentId);
$("#targetCustomerId").val(recipentId);
$("#add-form-order-note #recipentNote").val(recipentId);
var inbox = document.getElementById('message-chat-order-space');
inbox.innerHTML = '';
@ -544,15 +563,21 @@
}else{
stageWork += '<p>Этап ожидает завершения статуса от заказчика</p>';
}
stageWork += '<a href="#">Обратитьсяв арбитраж</a>';
if (data.secure) {
stageWork += '<a href="#" data-toggle="modal" data-target="#arbitration-add">Обратитьсяв арбитраж</a>';
}
$("#stagesWork").html(stageWork);
}else{
$("#completeWork").hide();
}
if(stagesCompleted.length == stagesResults.length){
if(stagesCompleted.length == stagesResults.length && stagesCompleted.length>0){
$("#leaveReview").show();
}
if (data.status == 'completed'){
$("#leaveReview").hide();
}
});
@ -699,6 +724,7 @@
});
$("#team-chat-form #chatText").val("");
$("#document-send").html("");
});
// Добавить сообщение для контакта
@ -720,27 +746,6 @@
$("#chat").val("");
});
$('#order-chat-add-message').on('click', function () {
var chatMessage = $("#chat-contractor-order #chat").val();
var recipentId = $("#chat-contractor-order #recipentOrderId").val();
var senderId = $("#chat-contractor-order #senderOrderId").val();
var orderId = $("#chat-contractor-order #orderId").val();
socket.add_contact_message({
"format_type": "add_message_order",
"data": {
"sender_id": senderId,
"recipent_id": recipentId,
"chat_message": chatMessage,
"order_id": orderId,
}
});
$("#chat-contractor-order #chat").val("");
});
});
</script>
{% endblock %}

@ -66,7 +66,7 @@
</div>
<form id="contact-chat-form">
<input type="hidden" value="{{ request.user.pk }}" name="senderId" id="senderId"/>
<input type="hidden" value="" name="recipentId" id="recipentId"/>
<input type="hidden" value="" name="recipentId" id="recipentId">
<textarea id="chat" name="chat_message" class="box-sizing"></textarea>
<div class="bunChat">
@ -124,26 +124,22 @@
</div>
<div class="col-lg-6 commChat">
<div id="message-chat-order-space">
</div>
<form id="chat-order-add" style="display:none;">
<div id="message-chat-order-space"></div>
<form id="chat-order-add">
<input type="hidden" name="senderId" id="senderId" value="{{ request.user.pk }}"/>
<input type="hidden" name="recipentId" id="recipentId" value=""/>
<input type="hidden" name="orderId" id="orderId" value=""/>
<input type="hidden" name="recipentId" id="recipentId">
<input type="hidden" name="orderId" id="orderId">
<textarea id="chat" class="box-sizing"></textarea>
<div class="bunChat">
<div class="setChat box-sizing">
<p>Прикрепить файл</p>
<span>
Не более 10 файлов с общим объемом 500мб
</span>
<div class="setChat box-sizing upload">
<input type="file" name="file" id="upload-document-order">
<p>Прикрепить файл</p>
</div>
<div id="document-send-order"></div>
<a href="#" id="order-chat-add-message">отправить</a>
</div>
</form>
</div>
<div class="col-lg-3 wrstepschat" id="order-stages-tab">
<p>Этапы работы</p>
@ -195,12 +191,14 @@
<a href="#">сохранить</a>
</div>
<!-- Review add -->
{% include 'review_add_modal.html' %}
<!-- -->
<!-- Arbitration add -->
{% include 'arbitration_modal.html' %}
<!-- -->
</div>
</div>
<!-- End block (chat order block) -->
@ -222,9 +220,7 @@
<script type="text/javascript">
$(function () {
var currentChatUser = {{ request.user.pk }};
var socket = new SocketHandler();
var form = document.getElementById('message_form');
var csrftoken = getCookie('csrftoken');
$("#reserve-button").on("click",function(e) {
e.preventDefault();
@ -355,12 +351,14 @@
});
if (statusNotAgreed) {
if(!data.secure) {
var orderSecureCheckbox = '';
var orderSecureCheckbox = '';
if(data.secure) {
orderSecureCheckbox = "checked";
}
htmlInbox += '<div class="box-sizing disTab">' +
'<div class="checkbox"><input name="secure" id="secureOrder"'+ orderSecureCheckbox +'type="checkbox" style="opacity:1">' +
'<div class="checkbox"><input name="secure" id="secureOrder" '+ orderSecureCheckbox +' type="checkbox" style="opacity:1">' +
'Перейти в режим безопасной сделки</div></div>';
}
htmlInbox +='<div class="textAreaBlock2 box-sizing disTab">' +
'<a href="#" data-sender-id="' + senderId +'" ' +
@ -384,6 +382,10 @@
stageWork += '<a href="#" class="closeStage" data-order-id="'+ orderId + '" data-sender-id="{{ request.user.pk }}"' +
' data-recipent-id="'+ recipentId + '" data-stage-id="'+ stage.id+'">Закрыть этап '+ stage.pos +'</a>';
}
if (data.secure){
stageWork += '<a href="#" data-toggle="modal" data-target="#arbitration-add">Обратитьсяв арбитраж</a>';
}
$("#stagesWork").html(stageWork);
}
@ -392,11 +394,17 @@
$("#reserveSpace").hide();
}
if(stagesCompleted.length == stagesResults.length){
if((stagesCompleted.length == stagesResults.length) && (stagesCompleted.length > 0)){
$("#leaveReview").show();
console.log("Все этапы завершены");
}else {
$("#leaveReview").hide();
}
if (data.status == 'completed'){
$("#leaveReview").hide();
}
});
}
@ -436,6 +444,29 @@
// Добавление этапов
$("#order-stages").on('click', "#addStagesForm", function (e) {
e.preventDefault();
var currentOrderId = $(this).attr('data-order-id');
var secureOrderEl = $("#secureOrder");
if(secureOrderEl.length > 0) {
var secOrderVal = false;
if (secureOrderEl.prop('checked')) {
secOrderVal = true;
}
$.ajax({
url: '/api/orders/' + currentOrderId + '/',
type: 'PATCH',
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))
},
data: {secure: secOrderVal},
dataType: 'json',
success: function (json){
},
error: function(e){
console.log(e);
}
});
}
$(".new-stages-form").each(function (i, v) {
$.ajax({
url: '/api/stages/',
@ -477,7 +508,7 @@
});
});
var currentOrderId = $(this).attr('data-order-id');
var currentRecipentId = $(this).attr('data-recipent-id');
var secureOrder = true
@ -553,6 +584,7 @@
var secureOrder = $(this).attr('data-secure-deal');
secureOrder = Boolean(secureOrder);
$("#chat-order-add #orderId").val(orderId);
$("#orderArbitrationId").val(orderId);
$("#projectReviewId").val(projectId);
$("#reserve-button").attr('data-order-id', orderId);
$("#targetContractorId").val(recipentId);
@ -642,50 +674,7 @@
$("#chat").val("");
});
// Добавление отзыва
$('#order-review-add').on('click', function (e) {
e.preventDefault();
e.stopPropagation();
var formData = $("#review-adds-form").serialize();
$.ajax({
url: '/api/reviews/',
type: 'POST',
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))
},
data: formData,
dataType: 'json',
success: function (json) {
console.log("Успешно");
console.log(json);
},
error: function (e) {
console.log('error');
console.log(e);
}
});
});
$('#order-chat-add-message').on('click', function (e) {
e.preventDefault();
var chatMessage = $("#chat-order-add #chat").val();
var recipentId = $("#chat-order-add #recipentId").val();
var senderId = $("#chat-order-add #senderId").val();
var orderId = $("#chat-order-add #orderId").val();
socket.add_contact_message({
"format_type": "add_message_order",
"data": {
"sender_id": senderId,
"recipent_id": recipentId,
"chat_message": chatMessage,
"order_id": orderId,
}
});
$("#chat-order-add #chat").val("");
});
});
var userId = '{{ request.user.pk }}';

@ -25,21 +25,21 @@
<p>Ваш отзыв</p>
<textarea id="text-new" name="text"></textarea>
{% if request.user.is_customer %}
<input type="text" name="from_customer" value="{{ request.user.pk }}"/>
<input type="hidden" name="from_customer" value="{{ request.user.pk }}"/>
<input type="hidden" name="from_contractor">
<input type="hidden" name="from_team">
<input type="text" name="target_contractor" id="targetContractorId"/>
<input type="text" name="target_customer">
<input type="hidden" name="target_contractor" id="targetContractorId"/>
<input type="hidden" name="target_customer">
<input type="hidden" name="target_team">
{% else %}
<input type="hidden" name="from_customer"/>
<input type="hidden" name="from_contractor" value="{{ request.user.pk }}">
<input type="text" name="target_customer">
<input type="hidden" name="target_customer" id="targetCustomerId">
<input type="hidden" name="target_contractor">
<input type="hidden" name="target_team">
<input type="hidden" name="from_team">
{% endif %}
<input type="text" name="project" id="projectReviewId"/>
<input type="hidden" name="target_team">
<input type="hidden" name="project" id="projectReviewId"/>
</div>
</div>
</div>

@ -2,6 +2,7 @@ from django.conf import urls
from .views import (
ChatUserView,
DocumentCreateView,
messages_delete,
)
@ -10,4 +11,5 @@ app_name = 'chat'
urlpatterns = [
urls.url(r'^$', ChatUserView.as_view(), name='chat-user'),
urls.url(r'^messages_delete/$', messages_delete, name='chat-messages_delete'),
urls.url(r'^create/$', DocumentCreateView.as_view()),
]

@ -0,0 +1,15 @@
import mimetypes
from django.core.urlresolvers import reverse
def serialize(instance, file_attr='file'):
obj = getattr(instance, file_attr)
return {
'id': instance.id,
'url': obj.url,
'name': obj.name,
'type': mimetypes.guess_type(obj.path)[0] or 'image/png',
'size': obj.size,
'deleteUrl': '/delete',
'deleteType': 'DELETE',
}

@ -1,22 +1,42 @@
import json
from django.shortcuts import render
from django.conf import settings
from django.views.generic import View
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 .models import Message
from .response import JSONResponse, response_mimetype
from .utils import serialize
from .models import Message, Documents
from projects.models import Order
from wallets.models import Transaction
from users.models import User, Team
class DocumentCreateView(CreateView):
model = Documents
fields = '__all__'
def form_valid(self, form):
# import code; code.interact(local=dict(globals(), **locals()))
self.object = form.save()
files = [serialize(self.object)]
data = {'files': files}
response = JSONResponse(data, mimetype=response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return response
def form_invalid(self, form):
# import code; code.interact(local=dict(globals(), **locals()))
data = json.dumps(form.errors)
return HttpResponse(content=data, status=400, content_type='application/json')
class ChatUserView(LoginRequiredMixin, View):
template_name = ''
def get(self, request, *args, **kwargs):
print(request.path)
# import code; code.interact(local=dict(globals(), **locals()))
user_id = request.GET.get('user_id',None)
if request.user.is_customer():

@ -13,6 +13,7 @@ from .models import (
ProjectFile,
Realty,
Stage,
Arbitration,
)
@ -32,6 +33,10 @@ class ProjectAdmin(admin.ModelAdmin):
form = ProjectAdminForm
class OrderAdmin(admin.ModelAdmin):
list_display = ('project', 'status', 'secure',)
class StageAdmin(admin.ModelAdmin):
list_display = ('name','status','pos','order','is_paid',)
@ -40,10 +45,11 @@ admin.site.register(Answer)
admin.site.register(Portfolio)
admin.site.register(PortfolioPhoto)
admin.site.register(Realty)
admin.site.register(Order)
admin.site.register(Order, OrderAdmin)
admin.site.register(Candidate)
admin.site.register(Stage, StageAdmin)
admin.site.register(BuildingClassfication)
admin.site.register(ConstructionType)
admin.site.register(Project, ProjectAdmin)
admin.site.register(ProjectFile)
admin.site.register(Arbitration)

@ -182,7 +182,7 @@ class RealtyForm(forms.ModelForm):
class PortfolioForm(forms.ModelForm):
duplicate = forms.BooleanField(required=False,label='Some label here')
duplicate = forms.BooleanField(required=False, label='Some label here')
images_ids = forms.CharField(required=True)
class Meta:

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-26 09:52
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('projects', '0016_merge'),
]
operations = [
migrations.CreateModel(
name='Arbitration',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField()),
('created', models.DateTimeField(default=django.utils.timezone.now)),
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='projects.Order')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Арбитраж',
'verbose_name_plural': 'Арбитраж',
},
),
]

@ -1,5 +1,6 @@
from django.utils import timezone
class LastAccessMixin(object):
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated():

@ -204,12 +204,13 @@ class Order(models.Model):
('process', 'В процессе'),
('completed', 'Завершен'),
)
contractor = models.ForeignKey(User, null=True, blank=True, related_name='orders')
team = models.ForeignKey(Team, null=True, blank=True, related_name='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
@ -219,6 +220,24 @@ class Order(models.Model):
verbose_name_plural = 'Заказы'
class Arbitration(models.Model):
user = models.ForeignKey(User)
text = models.TextField()
order = models.ForeignKey(Order)
created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.user.username
class Meta:
verbose_name = 'Арбитраж'
verbose_name_plural = 'Арбитраж'
unique_together = (
('user', 'order'),
)
STATUSES = (
('not_agreed','Не согласован'),
('in_process','В процессе'),
@ -269,7 +288,7 @@ class Portfolio(models.Model):
budget = models.DecimalField(max_digits=10, decimal_places=0, default=0, null=True, blank=True)
building_classification = models.ForeignKey(BuildingClassfication, related_name='portfolios', null=True, blank=True)
construction_type = models.ForeignKey(ConstructionType, related_name='portfolios', null=True, blank=True)
created = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(default=timezone.now, auto_now_add=True, auto_created=True)
currency = models.CharField(max_length=20, default='rur', choices=CURRENCIES, null=True, blank=True)
description = models.TextField()
location = TreeForeignKey('common.Location', related_name='portfolios', null=True, blank=True)
@ -279,19 +298,19 @@ class Portfolio(models.Model):
term_type = models.CharField(max_length=20, choices=TERMS, default='hour', null=True, blank=True)
user = models.ForeignKey(User, related_name='portfolios', null=True, blank=True)
worksell = models.BooleanField(default=False)
def __str__(self):
return self.name
def get_prev(self):
try:
return self.get_previous_by_created()
return self.get_previous_by_created(user=self.user)
except self.DoesNotExist:
return None
def get_next(self):
try:
return self.get_next_by_created()
return self.get_next_by_created(user=self.user)
except self.DoesNotExist:
return None

@ -84,7 +84,7 @@ class StageSerializer(ModelSerializer):
class OrderSerializer(ModelSerializer):
stages = StageSerializer(many=True)
stages = StageSerializer(many=True, read_only=True)
class Meta:
model = Order

@ -544,7 +544,11 @@
</div>
<div class="col-lg-3 retts">
{% ratings_widget answer.author.pk 'restList2' %}
{% if answer.author|class_name == 'User' %}
{% ratings_widget answer.author.pk 'restList2' %}
{% elif answer.author|class_name == 'Team'%}
{% ratings_team_widget answer.author.pk 'restList2' %}
{% endif %}
{% if answer.author|class_name == 'User' and answer.author.cro %}
<div class="sroUser sroExecutor">

@ -21,6 +21,7 @@ from .views import (
RejectProjectAnswerView,
sort_candidates,
PortfolioDetail,
ArbitrationCreateView,
)
app_name = 'projects'
@ -41,6 +42,7 @@ urlpatterns = [
urls.url(r'^reject-project-answer/(?P<pk>\d+)/$', RejectProjectAnswerView.as_view(), name='reject-project-answer'),
urls.url(r'^portfolio/create/$', contractor_portfolio_create, name='contractor-portfolio-create'),
urls.url(r'^arbitration/create/$', ArbitrationCreateView.as_view(), name='arbitration-create'),
urls.url(r'^portfolio/(?P<pk>\d+)/?$', PortfolioDetail.as_view(), name='contractor-portfolio-detail'),
urls.url(r'^portfolio/(?P<pk>\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'),

@ -6,7 +6,7 @@ from django.core.files.base import ContentFile
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse, reverse_lazy
from django.db.models import Q, F
from django.http import HttpResponseForbidden, HttpResponseRedirect, HttpResponse, Http404
from django.http import HttpResponseForbidden, JsonResponse, HttpResponseRedirect, HttpResponse, Http404
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import ListView, DetailView, CreateView, DeleteView, View, UpdateView, TemplateView, FormView
from hitcount.models import HitCount
@ -18,7 +18,7 @@ import pydash as _; _.map = _.map_; _.filter = _.filter_
import re
from .mixins import LastAccessMixin
from .models import Project, ProjectFile, Portfolio, PortfolioPhoto, Candidate, Answer, AnswerFile, AnswerMessage, Realty, Order
from .models import Arbitration, Project, ProjectFile, Portfolio, PortfolioPhoto, Candidate, Answer, AnswerFile, AnswerMessage, Realty, Order
from archilance import util
from archilance.mixins import BaseMixin
from users.models import User, Team
@ -59,8 +59,7 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
answer = _.first(_.filter(project_answers, lambda a: a.author == contractor))
if not answer:
try: team = contractor.team
except Team.DoesNotExist: team = None
team = util.get_related_or_none(contractor, 'team')
if team:
answer = _.first(_.filter(project_answers, lambda a: a.author == team))
@ -68,8 +67,10 @@ class ProjectDetailWithAnswerView(BaseMixin, View):
context.update({'answer': answer})
if not answer:
try: contractor.team; context.update({'can_answer_as_team': True})
except Team.DoesNotExist: pass
team = util.get_related_or_none(contractor, 'team')
if team:
context.update({'can_answer_as_team': True})
if request.GET.get('answer_as_team') == 'on':
context.update({'answer_as_team': True})
@ -154,8 +155,7 @@ class ProjectAnswerCreateMessageView(BaseMixin, View):
if isinstance(answer.author, User) and answer.author == request.user:
message.contractor_or_team = request.user
elif isinstance(answer.author, Team):
try: team = request.user.team
except Team.DoesNotExist: team = None
team = util.get_related_or_none(request.user, 'team')
if team and answer.author == team:
message.contractor_or_team = team
@ -658,7 +658,7 @@ class OfferOrderView(View):
order.team = answer.author
order.save()
redirect_url = reverse('chat:chat-user') + '?order_id=' + str(order.pk)
redirect_url = reverse('chat:chat-user') + '#order' + str(order.pk)
return HttpResponseRedirect(redirect_url)
@ -725,4 +725,26 @@ class PortfolioDetail(DetailView):
template_name = 'portfolio_detail.html'
class ArbitrationCreateView(CreateView):
model = Arbitration
fields = (
'order',
'user',
'text',
)
def form_valid(self, form):
if self.request.is_ajax():
self.object = form.save()
data = {
'status': 'ok',
}
return JsonResponse(data)
return super().form_valid(form)
def form_invalid(self, form):
if self.request.is_ajax():
return JsonResponse(form.errors, status=400)
return super().form_invalid(form)
# import code; code.interact(local=dict(globals(), **locals()))

@ -41,10 +41,11 @@ def ratings_widget(context, user_id, class_name=None):
@register.inclusion_tag("templatetags/ratings_widget.html", takes_context=True)
def ratings_team_widget(context, team_id):
def ratings_team_widget(context, team_id, class_name=None):
ratings = Team.objects.get(pk=team_id).rating
return {
'ratings': ratings,
'class_name': class_name,
'deals': Order.objects.filter(secure=True, team_id=team_id, status=1).count(),
'reviews_n': Review.objects.filter(target_team_id=team_id, type='neutral').count(),
'reviews_m': Review.objects.filter(target_team_id=team_id, type='negative').count(),

@ -0,0 +1 @@
default_app_config = 'reviews.apps.ReviewConfig'

@ -2,4 +2,7 @@ from django.apps import AppConfig
class ReviewConfig(AppConfig):
name = 'review'
name = 'reviews'
def ready(self):
import reviews.signals

@ -0,0 +1,31 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from users.models import User, Team
from ratings.models import HistoryRating
from .models import Review
@receiver(post_save, sender=Review)
def add_rating_review(sender, instance, created, **kwargs):
hs_rating = HistoryRating()
if instance.target_team:
hs_rating.team = instance.target_team
elif instance.target_contractor or instance.target_customer:
hs_rating.user = instance.target_contractor or instance.target_customer
if instance.type == 'positive':
hs_rating.rating = 1
elif instance.type == 'negative':
hs_rating.rating = -1
else:
hs_rating = 0
hs_rating.description = 'Изменения рейтинга после отзыва'
hs_rating.save()
count_reviews = Review.objects.filter(project=instance.project).count()
if count_reviews == 2:
order = instance.project.order
order.status = 'completed'
order.save()

@ -19,6 +19,7 @@
<div class="col-lg-3 col-lg-offset-2 listF1">
<p>Профиль</p>
<ul>
{% if not request.user.is_authenticated %}
<li>
<a href="{% url 'auth_login' %}">Войти</a>
</li>
@ -26,14 +27,21 @@
<li>
<a href="{% url 'registration_register' %}">Регистрация</a>
</li>
<li>
<a href="{% url 'password_reset_recover' %}">Востановить доступ</a>
</li>
<li>
<a href="{% url 'projects:customer-project-create' %}">Опубликовать проект</a>
</li>
{% else %}
{% if request.user.is_customer %}
<li>
<a href="{% url 'projects:customer-project-create' %}">Опубликовать проект</a>
</li>
{% else %}
<li>
<a href="{% url 'users:contractor-office' pk=request.user.pk %}">Мой офис</a>
</li>
{% endif %}
{% endif %}
</ul>
</div>

@ -147,11 +147,11 @@ class User(AbstractBaseUser, PermissionsMixin):
if self.location:
if self.location.level == 3:
return self.location.parent.parent.name + ', ' + self.location.name
else:
return None
else:
return None
elif self.location.level == 2:
return self.location.parent.name + ', ' + self.location.name
elif self.location.level == 1:
return self.location.name
@property
def is_staff(self):
return self.is_superuser

@ -142,7 +142,7 @@
type="submit"
name="{{ form.party_types.html_name }}"
value="{{ val }}"
class="{% if val == last_party_types %}active{% endif %} btn btn-default">
class="{% if val == last_party_types or not last_party_types and val == 'all' %}active{% endif %} btn btn-default">
{{ text }}
</button>
{% endfor %}

@ -17,12 +17,12 @@
<div class="buttonGP disTab">
<div class="btn-group valProject2 val-pro3" role="group">
<button type="button" class="btn btn-default">
{{ contractor.team.name }}
<span><mark>0</mark></span>
</button>
{% if not contractor.is_owner_team %}
{% if contractor.team %}
<button type="button" class="btn btn-default">
{{ contractor.team.name }}
<span><mark>0</mark></span>
</button>
{% else %}
<button type="button" class="btn btn-default add-group" data-toggle="modal" data-target="#myModal">
+ Добавить группу
</button>
@ -58,7 +58,7 @@
</div>
</div>
{% if contractor.is_owner_team %}
{% if contractor.team %}
<div class="projectsBlock disTab">
<div class="col-lg-12">
<div class="col-lg-3 divCol3">
@ -82,11 +82,11 @@
<div class="block-users">
<p>Состав группы</p>
{% for p in participants %}
{% for c in team_members %}
<div class="message-new">
<div class="imgMess">
{% if p.avatar %}
{% thumbnail p.avatar "60x60" crop="center" as im %}
{% if c.avatar %}
{% thumbnail c.avatar "60x60" crop="center" as im %}
<img src="{{ im.url }}" alt="mess-image">
{% endthumbnail %}
{% else %}
@ -95,10 +95,10 @@
</div>
<p class="nameMess" style="float: none">
<a href="{% url 'users:contractor-profile' p.pk %}">{{ p.get_full_name }}</a>
<a href="{% url 'users:contractor-profile' c.pk %}">{{ c.get_full_name }}</a>
</p>
<p><span>{{ p.get_popular_specialization }}</span></p>
<p><span>{{ c.get_popular_specialization }}</span></p>
</div>
{% empty %}
В группе пока нет участников
@ -110,15 +110,15 @@
<div class="col-lg-9 divCol9">
<div class="col-lg-4">
<p class="nameUser">{{ contractor.team.name }}</p>
<p class="cityUser">Россия, Москва</p>
<p class="cityUser">{{ contractor.get_location }}</p>
<p class="navv">На сайте {{ contractor.team.created }}</p>
<p class="navv">
Кол-во человек: <span>{{ participants_count }}</span>
Кол-во человек: <span>{{ team_member_count }}</span>
</p>
<p class="navv">
Выполненных проектов: <span>0</span>
Выполненных проектов: <span>{{ completed_project_count }}</span>
</p>
<div class="statusUser st-new">Свободен</div>

@ -364,24 +364,42 @@ class ContractorOfficeView(DetailView):
context_object_name = 'contractor'
form_class = TeamForm
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated() and request.user.is_contractor() and request.user.pk == int(kwargs.get('pk')):
return super().dispatch(request, *args, **kwargs)
else:
raise PermissionDenied
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form_team'] = self.form_class
if self.object.is_owner_team():
participants = self.object.team.contractors.all()
context['participants'] = participants
context['participants_count'] = len(participants)
contractor = self.object
if util.get_related_or_none(contractor, 'team'):
members = contractor.team.contractors.all()
context['team_members'] = members
context['team_member_count'] = len(members)
contractors = tuple(itertools.chain(participants, (self.object,)))
compl_proj = []
portfolios = []
work_sells = []
portfolios = Portfolio.objects.filter(user__in=contractors)
context['portfolios'] = portfolios
compl_proj.extend(tuple(o.project for o in contractor.orders.filter(status='completed')))
portfolios.extend(contractor.portfolios.all())
work_sells.extend(contractor.work_sell.all())
for c in members:
compl_proj.extend(tuple(o.project for o in c.orders.filter(status='completed')))
portfolios.extend(c.portfolios.all())
work_sells.extend(c.work_sell.all())
work_sells = WorkSell.objects.filter(contractor__in=contractors)
context['completed_project_count'] = len(compl_proj)
context['portfolios'] = portfolios
context['work_sells'] = work_sells
context['reviews'] = Review.objects.filter(target_contractor__in=contractors)
context['reviews'] = Review.objects.filter(target_contractor__in=itertools.chain((contractor,), members))
context['form_team'] = self.form_class
return context

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-08-26 17:25
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('work_sell', '0005_remove_worksell_img'),
]
operations = [
migrations.RemoveField(
model_name='worksell',
name='team',
),
]

@ -16,14 +16,13 @@ class WorkSell(models.Model):
budget = models.DecimalField(max_digits=10, decimal_places=0, default=0, null=True, blank=True)
building_classification = models.ForeignKey(BuildingClassfication, related_name='worksells', null=True, blank=True)
construction_type = models.ForeignKey(ConstructionType, related_name='worksells', null=True, blank=True)
contractor = models.ForeignKey(User, related_name='work_sell', null=True, blank=True) # TODO: Pluralize related name
created = models.DateTimeField(default=timezone.now)
contractor = models.ForeignKey(User, related_name='work_sell', null=True, blank=True) # TODO: Pluralize related name
created = models.DateTimeField(default=timezone.now, auto_now_add=True)
currency = models.CharField(max_length=20, default='rur', choices=CURRENCIES, null=True, blank=True)
description = models.TextField(blank=True)
location = TreeForeignKey('common.Location', related_name='worksells', null=True, blank=True)
name = models.CharField(max_length=255)
specialization = TreeForeignKey(Specialization, related_name='worksells', null=True, blank=True)
team = models.ForeignKey(Team, related_name='work_sells', null=True, blank=True)
term = models.IntegerField(default=0, null=True, blank=True)
term_type = models.CharField(max_length=20, choices=TERMS, default='hour', null=True, blank=True)
@ -35,13 +34,13 @@ class WorkSell(models.Model):
def get_prev(self):
try:
return self.get_previous_by_created()
return self.get_previous_by_created(contractor=self.contractor)
except self.DoesNotExist:
return None
def get_next(self):
try:
return self.get_next_by_created()
return self.get_next_by_created(contractor=self.contractor)
except self.DoesNotExist:
return None

Loading…
Cancel
Save