diff --git a/archilance/util.py b/archilance/util.py index a77321a..0deaf48 100644 --- a/archilance/util.py +++ b/archilance/util.py @@ -66,6 +66,13 @@ def get_related_or_none(obj, attr): return rel +def has_related(obj, attr): + try: + return bool(getattr(obj, attr)) + except ObjectDoesNotExist: + return False + + def get_attr_or_none(klass, *args, attr=None, **kwargs): object = get_or_none(klass, *args, **kwargs) diff --git a/assets/js/chat.js b/assets/js/chat.js index 0d5dbda..10977ed 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -1,6 +1,6 @@ var SocketHandler = function () { domain = domain.replace(':' + port, ''); - var url = 'ws://' + domain + '/chat/' + userId + '/'; + var url = 'ws://' + domain + ':8888/chat/' + userId + '/'; var sock = new WebSocket(url); var intervalId; sock.onopen = function () { diff --git a/projects/forms.py b/projects/forms.py index e16d95c..c2a8977 100644 --- a/projects/forms.py +++ b/projects/forms.py @@ -213,10 +213,16 @@ class ProjectAnswerForm(forms.ModelForm): if self.answer_as_team: self.fields['secure_deal_only'].label = 'Работаем только по безопасной сделке' - try: members = self.request.user.team.contractors.all() - except Team.DoesNotExist: members = () + contractor = self.request.user + members = contractor.team.contractors.all() if util.has_related(contractor, 'team') else () - portfolios = Portfolio.objects.filter(user__pk__in=tuple(m.pk for m in members)) + # 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))) self.fields['portfolios'].queryset = portfolios else: diff --git a/projects/models.py b/projects/models.py index aa8c6b3..46c8aa4 100644 --- a/projects/models.py +++ b/projects/models.py @@ -18,10 +18,10 @@ CURRENCIES = ( ) TERM_TYPES = ( - ('day', 'За день'), - ('hour', 'За час'), - ('month', 'За месяц'), - ('project', 'За проект'), + ('day', 'день'), + ('hour', 'час'), + ('month', 'месяц'), + ('project', 'проект'), ) TERM_TYPE_MORPHS = { diff --git a/projects/templates/project_detail.html b/projects/templates/project_detail.html index 2553141..4cdf3e9 100644 --- a/projects/templates/project_detail.html +++ b/projects/templates/project_detail.html @@ -24,7 +24,7 @@ {% endif %}
- +
{% if project.customer.avatar %} {% thumbnail project.customer.avatar "125x125" crop="center" as im %} @@ -193,25 +193,39 @@
- -
- {% if answer.author.avatar %} - {% thumbnail answer.author.avatar "125x125" crop="center" as im %} - execitor-image - {% endthumbnail %} - {% else %} - execitor-image - {% endif %} -
-
- -

- {% if answer.author|class_name == 'User' %} - {{ answer.author.get_full_name }} [{{ answer.author.username }}] - {% elif answer.author|class_name == 'Team' %} - {{ answer.author.name }} - {% endif %} -

+ {% if answer.author|class_name == 'User' %} + +
+ {% if answer.author.avatar %} + {% thumbnail answer.author.avatar "125x125" crop="center" as avatar %} + execitor-image + {% endthumbnail %} + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {{ answer.author.get_full_name }} [{{ answer.author.username }}] +

+ {% elif answer.author|class_name == 'Team' %} + +
+ {% if answer.author.owner.avatar %} + {% thumbnail answer.author.owner.avatar "125x125" crop="center" as avatar %} + execitor-image + {% endthumbnail %} + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {{ answer.author.name }} +

+ {% endif %} @@ -541,25 +555,39 @@
- -
- {% if answer.author.avatar %} - {% thumbnail answer.author.avatar "125x125" crop="center" as im %} - execitor-image - {% endthumbnail %} - {% else %} - execitor-image - {% endif %} -
-
- -

- {% if answer.author|class_name == 'User' %} - {{ answer.author.get_full_name }} [{{ answer.author.username }}] - {% elif answer.author|class_name == 'Team' %} - {{ answer.author.name }} - {% endif %} -

+ {% if answer.author|class_name == 'User' %} + +
+ {% if answer.author.avatar %} + {% thumbnail answer.author.avatar "125x125" crop="center" as avatar %} + execitor-image + {% endthumbnail %} + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {{ answer.author.get_full_name }} [{{ answer.author.username }}] +

+ {% elif answer.author|class_name == 'Team' %} + +
+ {% if answer.author.owner.avatar %} + {% thumbnail answer.author.owner.avatar "125x125" crop="center" as avatar %} + execitor-image + {% endthumbnail %} + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {{ answer.author.name }} +

+ {% endif %} @@ -615,7 +643,11 @@ {% endif %} - {% if not project.order.contractor and not project.order.team %} + {% if project.order.contractor and project.order.contractor == answer.author %} + Перейти к обсуждению + {% elif project.order.team and project.order.team == answer.author %} + Перейти к обсуждению + {% elif not project.order.contractor and not project.order.team %}
{% csrf_token %} @@ -711,25 +743,39 @@
- -
- {% if answer.author.avatar %} - {% thumbnail answer.author.avatar "125x125" crop="center" as im %} - execitor-image - {% endthumbnail %} - {% else %} - execitor-image - {% endif %} -
-
- -

- {% if answer.author|class_name == 'User' %} - {{ answer.author.get_full_name }} [{{ answer.author.username }}] - {% elif answer.author|class_name == 'Team' %} - {{ answer.author.name }} - {% endif %} -

+ {% if answer.author|class_name == 'User' %} + +
+ {% if answer.author.avatar %} + {% thumbnail answer.author.avatar "125x125" crop="center" as avatar %} + execitor-image + {% endthumbnail %} + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {{ answer.author.get_full_name }} [{{ answer.author.username }}] +

+ {% elif answer.author|class_name == 'Team' %} + +
+ {% if answer.author.owner.avatar %} + {% thumbnail answer.author.owner.avatar "125x125" crop="center" as avatar %} + execitor-image + {% endthumbnail %} + {% else %} + execitor-image + {% endif %} +
+
+ +

+ {{ answer.author.name }} +

+ {% endif %} @@ -785,7 +831,11 @@ {% endif %} - {% if not project.order.contractor and not project.order.team %} + {% if project.order.contractor and project.order.contractor == answer.author %} + Перейти к обсуждению + {% elif project.order.team and project.order.team == answer.author %} + Перейти к обсуждению + {% elif not project.order.contractor and not project.order.team %} {% csrf_token %} @@ -883,25 +933,39 @@
+
+ + {% csrf_token %} + + Восстановить + +
+
    {% for answer_file in answer.files.all %} diff --git a/projects/urls.py b/projects/urls.py index e46c32f..a4813c8 100644 --- a/projects/urls.py +++ b/projects/urls.py @@ -22,6 +22,7 @@ from .views import ( ProjectDetailWithAnswerView, ProjectFilterView, RejectProjectAnswerView, + RestoreProjectAnswerView, sort_candidates, TeamOfferOrder, ) @@ -30,23 +31,25 @@ app_name = 'projects' urlpatterns = [ urls.url(r'^$', ProjectFilterView.as_view(), name='project-filter'), - urls.url(r'^(?P\d+)/$', ProjectDetailWithAnswerView.as_view(), name='detail'), urls.url(r'^create/$', CustomerProjectCreateView.as_view(), name='customer-project-create'), + urls.url(r'^(?P\d+)/$', ProjectDetailWithAnswerView.as_view(), name='detail'), urls.url(r'^(?P\d+)/edit/$', CustomerProjectEditView.as_view(), name='customer-project-edit'), urls.url(r'^(?P\d+)/trash/$', CustomerProjectTrashView.as_view(), name='customer-project-trash'), - urls.url(r'^answer/move/archive/$', ContractorAnswerArchiveView.as_view(), name='contractor-answer-archive'), - urls.url(r'^portfolio/(?P\d+)/trash/$', ContractorPortfolioTrashView.as_view(), name='contractor-portfolio-trash'), urls.url(r'^(?P\d+)/restore/$', CustomerProjectRestoreView.as_view(), name='customer-project-restore'), urls.url(r'^(?P\d+)/delete/$', CustomerProjectDeleteView.as_view(), name='customer-project-delete'), urls.url(r'^create-answer-message/(?P\d+)/$', ProjectAnswerCreateMessageView.as_view(), name='create-answer-message'), urls.url(r'^reject-project-answer/(?P\d+)/$', RejectProjectAnswerView.as_view(), name='reject-project-answer'), + urls.url(r'^restore-project-answer/(?P\d+)/$', RestoreProjectAnswerView.as_view(), name='restore-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'^answer/move/archive/$', ContractorAnswerArchiveView.as_view(), name='contractor-answer-archive'), + + urls.url(r'^portfolio/create/$', contractor_portfolio_create, name='contractor-portfolio-create'), urls.url(r'^portfolio/(?P\d+)/?$', PortfolioDetail.as_view(), name='contractor-portfolio-detail'), urls.url(r'^portfolio/(?P\d+)/edit/$', ContractorPortfolioUpdateView.as_view(), name='contractor-portfolio-edit'), + urls.url(r'^portfolio/(?P\d+)/trash/$', ContractorPortfolioTrashView.as_view(), name='contractor-portfolio-trash'), urls.url(r'^candidate/add/(?P(\d+))/(?P(\d+))/$', add_candidate, name='add-candidate'), urls.url(r'^candidate/delete/(?P(\d+))/$', CandidateDeleteView.as_view(), name='delete-candidate'), diff --git a/projects/views.py b/projects/views.py index 593fa27..f29abe5 100644 --- a/projects/views.py +++ b/projects/views.py @@ -196,13 +196,7 @@ class ProjectAnswerCreateMessageView(BaseMixin, View): return redirect(redirect_to) -class RejectProjectAnswerView(BaseMixin, View): - def dispatch(self, request, *args, **kwargs): - if request.user.is_authenticated(): - return super().dispatch(request, *args, **kwargs) - else: - raise PermissionDenied - +class RejectProjectAnswerView(NoCsrfMixin, LoginRequiredMixin, BaseMixin, View): def post(self, request, *args, **kwargs): if request.user.is_contractor(): answer = None @@ -233,6 +227,32 @@ class RejectProjectAnswerView(BaseMixin, View): return redirect(redirect_to) +class RestoreProjectAnswerView(NoCsrfMixin, LoginRequiredMixin, BaseMixin, View): + def post(self, request, *args, **kwargs): + if request.user.is_contractor(): + answer = None + contractor_answer = util.get_or_none(request.user.contractor_answers, pk=kwargs.get('pk')) + + if contractor_answer: + answer = contractor_answer + elif request.user.team: + answer = util.get_or_none(request.user.team.answers, pk=kwargs.get('pk')) + + if not answer: + raise Http404 + elif request.user.is_customer(): + project = get_object_or_404(request.user.customer_projects, answers__pk=kwargs.get('pk')) + answer = get_object_or_404(project.answers, pk=kwargs.get('pk')) + + answer.rejected = False + answer.save() + + messages.info(request, 'Проект успешно восстановлен') + + redirect_to = request.POST.get('next') + return redirect(redirect_to) + + class ProjectFilterView(BaseMixin, View): template_name = 'project_filter.html' form_class = ProjectFilterForm @@ -526,7 +546,7 @@ class ContractorPortfolioTrashView(View): if form.is_valid(): portfolio = form.cleaned_data.get('pk') portfolio.delete() - messages.info(request, 'Портфолио удален') + messages.info(request, 'Портфолио удалено') else: messages.info(request, 'Произошла ошибка:
    {msg}
    '.format(msg=pformat(form.errors))) diff --git a/templates/partials/base.html b/templates/partials/base.html index ae06041..3ba3260 100644 --- a/templates/partials/base.html +++ b/templates/partials/base.html @@ -87,7 +87,7 @@ if ((queryString.indexOf('/chat') != 0) && (queryString.indexOf('/users/contractor-office/510/work-projects') != 0)) { domain = domain.replace(':' + port, ''); - var url = 'ws://' + domain + '/chat/' + userId + '/'; + var url = 'ws://' + domain + ':8888/chat/' + userId + '/'; var sock = new WebSocket(url); var intervalId; sock.onopen = function () { diff --git a/users/templates/contractor_office.html b/users/templates/contractor_office.html index 52d5b27..fe6d3cc 100644 --- a/users/templates/contractor_office.html +++ b/users/templates/contractor_office.html @@ -268,7 +268,7 @@ var portfUrl = new URI('/api/portfolios/') var workSellUrl = new URI('/api/work-sells/') - var pageSize = 3 + var pageSize = {% if TESTING %}99999{% else %}9{% endif %} $.get('/api/teams/' + teamId + '/').then(function(res) { Array.prototype.push.apply(contractorIds, _.map(function(contractor) { @@ -283,7 +283,7 @@ portfUrl.setQuery('user__id__in', _.join(',', contractorIds)) portfUrl.setQuery('page_size', pageSize) - portfUrl.setQuery('page', query.page ? Number(query.page) + 1 : 2) + portfUrl.setQuery('page', query.page ? Number(query.page) + 1 : 1) $.get(portfUrl.href()) .then(function(res) { @@ -309,7 +309,7 @@ workSellUrl.setQuery('contractor', contractorId) workSellUrl.setQuery('page_size', pageSize) - workSellUrl.setQuery('page', query.page ? Number(query.page) + 1 : 2) + workSellUrl.setQuery('page', query.page ? Number(query.page) + 1 : 1) $.get(workSellUrl.href()).then(function(res) { _.each(function(ws) { diff --git a/users/templates/team_profile.html b/users/templates/team_profile.html index 54e372d..3f9cc80 100644 --- a/users/templates/team_profile.html +++ b/users/templates/team_profile.html @@ -290,228 +290,6 @@ $('a[data-toggle="tab"]').on('click', function($evt) { // Better handle "shown.bs.tab" event? window.location.hash = $(this).attr('href') }) - - - - //----------------------------------------------------------------- - - -{# var userId = '{{ contractor.pk }}';#} -{# #} -{# var hash = window.location.hash;#} -{# #} -{# if (hash == '#open-contact') {#} -{# $("#contact-contactor-modal").modal();#} -{# }#} -{# #} -{# $('#resume-edit-form').on('submit', function (e) {#} -{# e.preventDefault();#} -{# var resumeEditUrl = "{% url 'users:contractor-resume-update' contractor.contractor_resume.pk %}";#} -{# var dataSerializer = $(this).serialize();#} -{# #} -{# $.ajax({#} -{# url: resumeEditUrl,#} -{# method: 'POST',#} -{# beforeSend: function (xhr) {#} -{# xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))#} -{# },#} -{# data: dataSerializer,#} -{# dataType: 'json',#} -{# success: function (data) {#} -{# console.log(data);#} -{# if (data.status == 'ok') {#} -{# $("#resume-text-out").html(data.text);#} -{# $("#resume-text-edit").modal('hide');#} -{# #} -{# }#} -{# },#} -{# error: function (jqXHR, exception) {#} -{# console.log(jqXHR);#} -{# console.log(exception);#} -{# console.log(jqXHR.statusCode);#} -{# }#} -{# #} -{# });#} -{# });#} -{# #} -{# #} -{# $("#diplom-cro-gallery").on('click', '.delete-resume-file',function(e){#} -{# e.preventDefault();#} -{# #} -{# var _this = $(this);#} -{# var deleteResumeUrl = '/api/contractorresumefiles/' + $(this).attr('data-id') + '/';#} -{# $.ajax({#} -{# url:deleteResumeUrl,#} -{# type: 'DELETE',#} -{# beforeSend: function (xhr) {#} -{# xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'))#} -{# },#} -{# success: function(json){#} -{# _this.parent().parent().parent().remove();#} -{# console.log(json);#} -{# },#} -{# error: function(jqXHR, e){#} -{# console.log(jqXHR.statusCode);#} -{# }#} -{# });#} -{# });#} -{# #} -{# #} -{# $('#worksell-add-form').on('submit', function (e) {#} -{# e.preventDefault();#} -{# var dataSerializer = $(this).serialize();#} -{# $.ajax({#} -{# url: '/work_sell/create/',#} -{# method: 'POST',#} -{# data: dataSerializer,#} -{# dataType: 'json',#} -{# success: function (data) {#} -{# if (data.status == 'ok') {#} -{# location.reload();#} -{# }#} -{# #} -{# },#} -{# error: function (jqXHR, exception) {#} -{# console.log(jqXHR.statusCode);#} -{# console.log(jqXHR);#} -{# }#} -{# #} -{# })#} -{# });#} -{# #} -{# #} -{# $('#portfolio-add-form').on('submit', function (e) {#} -{# e.preventDefault();#} -{# var dataSerializer = $(this).serialize();#} -{# #} -{# $.ajax({#} -{# url: '/projects/portfolio/create/',#} -{# method: 'POST',#} -{# data: dataSerializer,#} -{# dataType: 'json',#} -{# success: function (data) {#} -{# if (data.status == 'ok') {#} -{# $('#portfolio-add-form').each(function () {#} -{# this.reset();#} -{# });#} -{# location.reload();#} -{# } else if (data.status == 'no') {#} -{# #} -{# $.each(data.form_errors, function (k, v) {#} -{# $('.error-' + k).html(v).show();#} -{# });#} -{# }#} -{# },#} -{# error: function (jqXHR, exception) {#} -{# console.log(jqXHR.statusCode);#} -{# }#} -{# });#} -{# });#} -{# var url = '/work_sell/basic/';#} -{# var csrftoken = $.cookie('csrftoken');#} -{# #} -{# $("#upload-resume").on('change',function(e){#} -{# $("#resume-success").html("");#} -{# var formData = new FormData($(this).closest("form"));#} -{# formData.append('resume_file', e.target.files[0]);#} -{# console.log(formData);#} -{# console.log(e.target.files[0]);#} -{# $.ajax({#} -{# url:'/api/contractorresume/{{ contractor.contractor_resume.pk }}/',#} -{# method:'PUT',#} -{# beforeSend: function (xhr) {#} -{# xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));#} -{# },#} -{# data: formData,#} -{# cache: false,#} -{# dataType: 'json',#} -{# processData: false,#} -{# contentType: false,#} -{# success: function(data){#} -{# $("#resume-success").html("Файл для резюме успешно загружен!");#} -{# $(".download-summ").css('display','block').attr('href',data.resume_file);#} -{# console.log(data);#} -{# },#} -{# error: function(jqXHR){#} -{# console.log(jqXHR);#} -{# }#} -{# });#} -{# });#} -{# #} -{# $('#fileupload').fileupload({#} -{# url: url,#} -{# crossDomain: false,#} -{# beforeSend: function (xhr, settings) {#} -{# $('#progress-portfolio .progress-bar').css(#} -{# 'width',#} -{# '0%'#} -{# );#} -{# if (!csrfSafeMethod(settings.type)) {#} -{# xhr.setRequestHeader("X-CSRFToken", csrftoken);#} -{# }#} -{# },#} -{# dataType: 'json',#} -{# done: function (e, data) {#} -{# console.log(data);#} -{# $.each(data.result.files, function (index, file) {#} -{# var img = $('').attr('src', file.url).appendTo("#files");#} -{# console.log(file);#} -{# var currentValue = $("#upload-files-pk").val();#} -{# currentValue += file.id + ';';#} -{# $("#upload-files-pk").val(currentValue);#} -{# #} -{# });#} -{# },#} -{# progressall: function (e, data) {#} -{# var progress = parseInt(data.loaded / data.total * 100, 10);#} -{# console.log(progress);#} -{# $('#progress-portfolio .progress-bar').css(#} -{# 'width',#} -{# progress + '%'#} -{# );#} -{# }#} -{# }).prop('disabled', !$.support.fileInput)#} -{# .parent().addClass($.support.fileInput ? undefined : 'disabled');#} -{# #} -{# $('#fileupload-worksell').fileupload({#} -{# url: url,#} -{# crossDomain: false,#} -{# beforeSend: function (xhr, settings) {#} -{# $('#progress-worksell .progress-bar').css(#} -{# 'width',#} -{# '0%'#} -{# );#} -{# if (!csrfSafeMethod(settings.type)) {#} -{# xhr.setRequestHeader("X-CSRFToken", csrftoken);#} -{# }#} -{# },#} -{# dataType: 'json',#} -{# done: function (e, data) {#} -{# console.log(data);#} -{# $.each(data.result.files, function (index, file) {#} -{# var img = $('').attr('src', file.url).appendTo("#files-worksell");#} -{# console.log(file);#} -{# var currentValue = $("#upload-files-worksell-pk").val();#} -{# currentValue += file.id + ';';#} -{# $("#upload-files-worksell-pk").val(currentValue);#} -{# #} -{# });#} -{# },#} -{# progressall: function (e, data) {#} -{# var progress = parseInt(data.loaded / data.total * 100, 10);#} -{# console.log(progress);#} -{# $('#progress-worksell .progress-bar').css(#} -{# 'width',#} -{# progress + '%'#} -{# );#} -{# }#} -{# }).prop('disabled', !$.support.fileInput)#} -{# .parent().addClass($.support.fileInput ? undefined : 'disabled');#} -{# #} -{# function csrfSafeMethod(method) {#} -{# // these HTTP methods do not require CSRF protection#} -{# return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));#} -{# }#} }()) {% endblock %} diff --git a/users/views.py b/users/views.py index 9406152..c8423a3 100644 --- a/users/views.py +++ b/users/views.py @@ -352,7 +352,7 @@ class ContractorFilterView(BaseMixin, View): return render(request, self.template_name, context) -class ContractorProfileView(DetailView): +class ContractorProfileView(BaseMixin, DetailView): model = User worksell_form_class = WorkSellForm portfolio_form_class = PortfolioForm @@ -380,7 +380,7 @@ class ContractorProfileView(DetailView): return context -class ContractorOfficeView(DetailView): +class ContractorOfficeView(BaseMixin, DetailView): model = User template_name = 'contractor_office.html' context_object_name = 'contractor' @@ -416,7 +416,7 @@ class ContractorOfficeView(DetailView): return context -class TeamProfileView(DetailView): +class TeamProfileView(BaseMixin, DetailView): model = Team template_name = 'team_profile.html' context_object_name = 'team'