You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

381 lines
14 KiB

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Q
from django.core.exceptions import ValidationError
from django.utils import timezone
from hitcount.models import HitCountMixin
from mptt.models import TreeForeignKey
import pydash as _; _.map = _.map_; _.filter = _.filter_
from users.models import User, Team
from specializations.models import Specialization
CURRENCIES = (
('rur', 'RUR'),
('usd', 'USD'),
('eur', 'EUR'),
)
TERM_TYPES = (
('day', 'день'),
('hour', 'час'),
('month', 'месяц'),
('project', 'проект'),
)
TERM_TYPE_MORPHS = {
'day': 'день,дня,дней',
'hour': 'час,часа,часов',
'month': 'месяц,месяца,месяцев',
'project': 'проект,проекта,проектов'
}
class BuildingClassfication(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Тип здания'
verbose_name_plural = 'Типы зданий'
class ConstructionType(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Вид строительства'
verbose_name_plural = 'Виды строительства'
class Realty(models.Model):
building_classification = models.ForeignKey(BuildingClassfication, related_name='realties')
construction_type = models.ForeignKey(ConstructionType, related_name='realties')
location = TreeForeignKey('common.Location', related_name='realties', null=True, blank=True)
name = models.CharField(max_length=255)
user = models.ForeignKey(User, related_name='realties') # Do we actually need this field?
def __str__(self):
return self.name
class Meta:
verbose_name = 'Объект'
verbose_name_plural = 'Объекты'
class Project(models.Model, HitCountMixin):
WORK_TYPES = (
(1, 'Проектирование'),
(2, 'Техническое сопровождение')
)
DEAL_TYPES = (
('secure_deal', 'Безопасная сделка'), # "Безопасная сделка (с резервированием бюджета)"
('direct_payment', 'Прямая оплата'), # "Прямая оплата Исполнителю на его кошелек/счет"
)
STATES = (
('active', 'Активный'),
('trashed', 'В корзине'),
('deleted', 'Удален'),
)
budget = models.DecimalField(max_digits=10, decimal_places=0)
budget_by_agreement = models.BooleanField(default=False)
created = models.DateTimeField(default=timezone.now)
cro = models.BooleanField(default=False)
currency = models.CharField(max_length=20, default='rur', choices=CURRENCIES)
customer = models.ForeignKey(User, related_name='customer_projects')
deal_type = models.CharField(max_length=20, default='secure_deal', choices=DEAL_TYPES)
name = models.CharField(max_length=255)
price_and_term_required = models.BooleanField(default=False)
realty = models.ForeignKey(Realty, blank=True, null=True, related_name='projects')
specialization = TreeForeignKey(Specialization, related_name='projects')
state = models.CharField(default='active', max_length=20, choices=STATES)
term = models.IntegerField(default=0)
term_type = models.CharField(max_length=20, choices=TERM_TYPES, default='hour')
text = models.TextField(blank=True)
work_type = models.IntegerField(default=1, choices=WORK_TYPES)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Проект'
verbose_name_plural = 'Проекты'
ordering = ('-created',)
def secure_deal(self):
return self.deal_type == 'secure_deal'
def get_team_answers(self):
return _.filter(self.answers.all(), lambda a: isinstance(a.author, Team))
class ProjectFile(models.Model):
file = models.FileField(upload_to='projects/project_files/')
project = models.ForeignKey(Project, related_name='files', blank=True, null=True)
class Meta:
verbose_name = 'Файл проекта'
verbose_name_plural = 'Файлы проектов'
def __str__(self):
return self.file and self.file.url or self.pk
class Answer(models.Model):
budget = models.DecimalField(max_digits=10, decimal_places=0, blank=True, null=True)
created = models.DateTimeField(default=timezone.now)
currency = models.CharField(max_length=5, choices=CURRENCIES, blank=True, null=True)
portfolios = models.ManyToManyField('Portfolio', related_name ='answers', blank=True)
project = models.ForeignKey(Project, related_name='answers')
secure_deal_only = models.BooleanField(default=False)
term = models.IntegerField(blank=True, null=True)
term_type = models.CharField(max_length=10, choices=TERM_TYPES, blank=True, null=True)
is_archive = models.BooleanField(default=False)
rejected = models.BooleanField(default=False)
content_type = models.ForeignKey(ContentType, limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team'))
object_id = models.IntegerField()
author = GenericForeignKey('content_type', 'object_id')
def get_first_message(self):
message = self.messages.first()
if message:
return message.text
def __str__(self):
return "{author}'s answer ({id})".format(author=type(self.author).__name__, id=self.pk)
class Meta:
verbose_name = 'Отклик на проект'
verbose_name_plural = 'Отклики на проекты'
ordering = ('-created',)
class AnswerMessage(models.Model):
# Get a message's author:
#
# customer: `message.is_sender_customer and message.answer.project.customer`
# contractor/team: `not message2.is_sender_customer and message2.contractor_or_team`
answer = models.ForeignKey(Answer, related_name='messages')
created = models.DateTimeField(default=timezone.now)
is_sender_customer = models.BooleanField(default=False)
text = models.TextField()
content_type = models.ForeignKey(
ContentType,
limit_choices_to=Q(app_label='users', model='user') | Q(app_label='users', model='team'),
null=True,
blank=True,
)
object_id = models.IntegerField(null=True, blank=True)
contractor_or_team = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return str(self.pk)
class Meta:
ordering = ['created']
verbose_name = 'Отклики на проекты -- переписка'
verbose_name_plural = 'Отклики на проекты -- переписки'
class AnswerFile(models.Model):
answer = models.ForeignKey(Answer, related_name='files', blank=True, null=True)
name = models.CharField(max_length=255)
file = models.FileField(upload_to='projects/answer_files/')
class Meta:
verbose_name = 'Файл для отклика'
verbose_name_plural = 'Файлы для откликов'
def __str__(self):
return self.file and self.file.url or self.pk
class Order(models.Model):
STATUSES = (
('created', 'Создан'),
('process', 'В процессе'),
('completed', 'Завершен'),
)
contractor = models.ForeignKey(User, null=True, blank=True, related_name='orders') # Related name should've been "contractor_orders"
created = models.DateTimeField(default=timezone.now)
project = models.OneToOneField(Project, related_name='order')
secure = models.BooleanField(default=False)
status = models.CharField(max_length=30, choices=STATUSES, default='created')
team = models.ForeignKey(Team, null=True, blank=True, related_name='orders')
def __str__(self):
return self.project.name
class Meta:
verbose_name = 'Заказ'
verbose_name_plural = 'Заказы'
def get_contractor_owner(self):
if self.contractor:
return self.contractor.pk
elif self.team:
return self.team.owner.pk
else:
return None
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', 'Не согласован'),
('send_approve', 'На согласовании'),
('cancel_approve', 'Исполнитель отказался'),
('in_process', 'В процессе'),
('completed', 'Завершен'),
)
from .validators import validate_term
class Stage(models.Model):
cost = models.DecimalField(max_digits=10, decimal_places=0)
cost_type = models.CharField(max_length=5, choices=CURRENCIES, default='rur')
name = models.CharField(max_length=255)
order = models.ForeignKey(Order, related_name='stages')
result = models.CharField(max_length=255)
term = models.DateField()
term_type = models.CharField(max_length=10, choices=TERM_TYPES, default='hour')
status = models.CharField(choices=STATUSES, max_length=30, default='not_agreed')
created = models.DateTimeField(default=timezone.now)
approve_time = models.DateTimeField(null=True, blank=True)
pos = models.IntegerField(default=0, null=True, blank=True)
is_paid = models.BooleanField(default=False)
close_contractor = models.BooleanField(default=False)
close_customer = models.BooleanField(default=False)
def __str__(self):
return self.name
def clean(self, *args, **kwargs):
pass
# stage_last = self.__class__.objects.filter(order=self.order).order_by('-pos')[:1]
# if stage_last:
# stage_last = stage_last[0]
# if stage_last.term > self.term:
# raise ValidationError({'term':'Дата не должна быть меньше даты предыдущео этапа'})
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
class Meta:
ordering = ['pos']
verbose_name = 'Этап'
verbose_name_plural = 'Этапы'
class Candidate(models.Model):
answer = models.ForeignKey(Answer, related_name='candidates') # TODO: Swap to "OneToOneField"
project = models.ForeignKey(Project, related_name='candidates') # TODO: Remove this redundant field at all (we've got "candidate.answer.project")
status = models.BooleanField(default=False)
position = models.PositiveIntegerField(default=0)
def __str__(self):
return str(self.answer.pk)
class Meta:
ordering = ('position',)
verbose_name = 'Кандидат'
verbose_name_plural = 'Кандидаты'
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(auto_now_add=True, auto_created=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='portfolios', null=True, blank=True)
name = models.CharField(max_length=255)
specialization = TreeForeignKey(Specialization, related_name='portfolios', null=True, blank=True)
term = models.IntegerField(default=0, null=True, blank=True)
term_type = models.CharField(max_length=20, choices=TERM_TYPES, 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(user=self.user)
except self.DoesNotExist:
return None
def get_next(self):
try:
return self.get_next_by_created(user=self.user)
except self.DoesNotExist:
return None
class Meta:
ordering = ['-created']
verbose_name = 'Портфолио'
verbose_name_plural = 'Портфолио'
def get_cover(self):
photo = self.photos.first()
return photo and photo.img # WTF? We could at leat return a URL, not an object
class PortfolioPhoto(models.Model):
img = models.ImageField(upload_to='projects/portfolio/')
portfolio = models.ForeignKey(Portfolio, related_name='photos')
class Meta:
verbose_name = 'Фото портфолио'
verbose_name_plural = 'Фото портфолио'
def __str__(self):
return self.img and self.img.url or str(self.img)
class ProjectWorkTypeSuggestion(models.Model):
name = models.CharField(max_length=255, verbose_name='Название')
commentary = models.TextField(blank=True, verbose_name='Комментарий')
processed = models.BooleanField(default=False, verbose_name='Рассмотрено?')
username = models.CharField(max_length=255, blank=True, verbose_name='Имя пользователя')
email = models.EmailField(verbose_name='Email')
class Meta:
verbose_name = 'Предложение типов работ (проектов)'
verbose_name_plural = 'Предложения типов работ (проектов)'
def __str__(self):
return self.name