from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
from hashlib import md5
from datetime import datetime, date
from PIL import Image
import pandas as pd
from profiles.models import StudentInfo, same_users_list
TOTAL_TAX = 0.86
[docs]def get_name(self: User):
try:
name = self.userprofile.student_info.fio
except Exception:
name = "{} {}".format(self.last_name, self.first_name)
if self.social_auth.exists():
name += " | " + self.social_auth.all()[0].provider.split('-oauth')[0]
return name
User.add_to_class("__str__", get_name)
[docs]class Category(models.Model):
name = models.CharField("Название", max_length=100)
reason = models.CharField("Причина (для заявления)", max_length=100)
max_sum = models.IntegerField("Макс. сумма", default=20000)
max_quantity = models.IntegerField("Макс. раз за семестр", default=1)
notifications = models.BooleanField("Уведомления о новых заявлениях", default=True)
show_to_students = models.BooleanField('Показывать студентам', default=True)
is_senate = models.BooleanField("Сенатская", default=False)
class Meta:
verbose_name = "категория"
verbose_name_plural = "категории"
def __str__(self):
return self.name
[docs]def is_image(file):
try:
trial_image = Image.open(file)
trial_image.verify() # raises exception if there are errors
return True
except Exception:
return False
# TODO add_dttm changes EVERY time when model is saved, so instead of setting it as auto_now_add=True it's better to set im manually
[docs]class AidRequest(models.Model):
WAITING = 1
ACCEPTED = 2
DECLINED = 3
INFO_NEEDED = 4
PRE_ACCEPTED = 5
AID_REQUEST_STATUS = (
(WAITING, "Заявление рассматривается"),
(ACCEPTED, "Заявление одобрено"),
(DECLINED, "В заявлении отказано"),
(INFO_NEEDED, "Необходимо уточнить данные"),
(PRE_ACCEPTED, "Предварительно одобрено"),
)
author = models.ForeignKey(User, blank=True, null=True, verbose_name="Автор", related_name='author')
applicant = models.ForeignKey(User, blank=True, null=True, verbose_name='Получатель') # find out how to add applicant to form before validation
category = models.ForeignKey(Category, verbose_name="Категория")
reason = models.TextField("Причина", max_length=2048)
req_sum = models.FloatField("Запрошенная сумма", blank=True, null=True)
urgent = models.BooleanField("Срочно", default=False)
accepted_sum = models.FloatField("Одобренная сумма", blank=True, null=True)
status = models.IntegerField("Статус заявления", choices=AID_REQUEST_STATUS, default=WAITING)
add_dttm = models.DateTimeField("Дата подачи", auto_now_add=True)
examination_dttm = models.DateTimeField("Дата рассмотрения", blank=True, null=True)
payment_dt = models.DateField("Дата выплаты", blank=True, null=True)
examination_comment = models.TextField("Комментарий комиссии", blank=True, null=True)
submitted_paper = models.BooleanField("Принес заявление", default=False)
paid_with_cash = models.BooleanField("Заплатили наличными", default=False)
verified = models.BooleanField("Заявление проверено", default=False)
[docs] def can_view(self, user):
if not user.is_authenticated:
return False
if self.applicant == user \
or user.userprofile.student_info and self.applicant.userprofile.student_info == user.userprofile.student_info \
or user.groups.filter(name="finance").exists() or user.is_superuser:
return True
return False
[docs] def get_absolute_url(self):
return reverse('fin_aid:aid_request_detail', args=[self.id])
def __str__(self):
try:
return "{}: заявление от {} по категории {} на сумму {}".format(self.applicant, self.add_dttm.date(), self.category, self.req_sum)
except Exception:
return "Зявление на матпомощь"
class Meta:
verbose_name = "заявление на матпомощь"
verbose_name_plural = "заявления на матпомощь"
# returns links to images, related to this aidrequest
# TODO rewrite using new AidDocument.is_image field?
images_tags.allow_tags = True
images_tags.short_description = "Приложенные изображения"
# creates csv with all accepted applications for this month
@staticmethod
[docs] def to_csv(filename, year=None, month=None):
df = pd.DataFrame(columns=["FIO", "group", "req_sum", "Исх. сумма", "Реал. сумма", "За что", "заявление",
"Комментарии", "e-mail", "text"])
if year is None:
year = timezone.now().year
if month is None:
month = timezone.now().month
for request in AidRequest.objects.filter(status=AidRequest.ACCEPTED,
payment_dt__year=year,
payment_dt__month=month,
paid_with_cash=False).order_by('category'):
dct = {
"req_sum": request.req_sum,
"Исх. сумма": int(request.accepted_sum/TOTAL_TAX) if request.accepted_sum != 0 else 0,
"Реал. сумма": int(request.accepted_sum),
"За что": request.category.name,
"Комментарии": request.examination_comment,
"text": "",
}
student_info = request.applicant.userprofile.student_info
if student_info:
dct.update({
"FIO": student_info.fio,
"group": student_info.group,
"e-mail": student_info.phystech,
})
else:
fio = request.applicant.last_name + request.applicant.first_name + request.applicant.userprofile.middlename
if fio:
dct.update({"FIO": fio})
else:
dct.update({"FIO": request.applicant.username})
if request.submitted_paper:
dct.update({"заявление":"+"})
df = df.append(dct, ignore_index=True)
df.to_csv(filename)
@property
def status_text(self):
if self.status == AidRequest.PRE_ACCEPTED:
display_status = AidRequest.WAITING
else:
display_status = self.status
for status, text in AidRequest.AID_REQUEST_STATUS:
if status == display_status:
return text
return None
@classmethod
[docs] def user_requests(cls, user: User):
users = same_users_list(user)
return cls.objects.filter(applicant__in=users).order_by("-add_dttm")
# separate function as it's used in create_paper
[docs]def user_hash(user):
secret = md5(str(user.id).encode("utf-8")).hexdigest()
secret = md5((secret + user.username).encode("utf-8")).hexdigest()
return secret
[docs]def document_path(instance, filename):
return "aid_docs/user_{}/{}".format(user_hash(instance.request.applicant), filename)
# TODO if user can harm us with custom filename we should replace user-given filename
[docs]class AidDocument(models.Model):
file = models.FileField("Документ", upload_to=document_path)
# filename = models.CharField("Имя файла", max_length=100)
request = models.ForeignKey(AidRequest)
is_application_paper = models.BooleanField("Заявление на матпомощь", default=False)
is_image = models.BooleanField("Является изображением", default=False)
class Meta:
verbose_name = "потверждающий документ"
verbose_name_plural = "подтверждающие документы"
def __str__(self):
return "Документ {} к заявлению {}".format(self.file.name, self.request)
def _get_next_date_naive(dt=None, t='payment'):
if not dt:
dt = date.today()
pay_day = 28
edge_day = 14
application_deadline = 11
if t == 'payment':
day = pay_day
elif t == 'deadline':
day = edge_day
elif t == 'student_deadline':
day = application_deadline
else:
raise ValueError
year = dt.year
if dt.day <= edge_day:
month = dt.month
else:
if dt.month < 12:
month = dt.month + 1
else:
month = 1
year = dt.year + 1
result_dt = date(year, month, day)
return result_dt
def _get_default_year():
return date.today().year
def _get_default_month():
return date.today().month
def _get_default_payment_dt():
return date(datetime.now().year, datetime.now().month, 28)
def _get_default_deadline_dt():
return date(datetime.now().year, datetime.now().month, 14)
[docs]class MonthlyData(models.Model):
MONTH = [
(1, "Январь"), (2, "Февраль"), (3, "Март"), (4, "Апрель"), (5, "Май"), (6, "Июнь"),
(7, "Июль"), (8, "Август"), (9, "Сентябрь"), (10, "Октябрь"), (11, "Ноябрь"), (12, "Декабрь"),
]
year = models.IntegerField("Год", default=_get_default_year)
month = models.IntegerField("Месяц", default=_get_default_month, choices=MONTH)
limit = models.FloatField("Лимит")
deadline_dt = models.DateField("Дэдлайн по приказу", default=_get_default_deadline_dt)
student_deadline_dt = models.DateField("Дэдлайн по заявлениям", default=_get_default_deadline_dt)
payment_dt = models.DateField("Дата выплаты матпомощи", default=_get_default_payment_dt)
def __str__(self):
return "{} {}".format(self.get_month_display(), self.year)
class Meta:
verbose_name = "Информация о месяце"
verbose_name_plural = "Лимиты по месяцам"
@classmethod
[docs] def current(cls):
deadline = get_next_date(date.today(), 'deadline')
return cls.objects.get(year=deadline.year, month=deadline.month)
@classmethod
[docs] def next(cls):
deadline = get_next_date(get_next_date(date.today(), 'payment'), 'deadline')
return cls.objects.get(year=deadline.year, month=deadline.month)
@property
def sum_used(self):
requests = AidRequest.objects.filter(status__in=[AidRequest.ACCEPTED, AidRequest.PRE_ACCEPTED], paid_with_cash=False, payment_dt__year=self.year,
payment_dt__month=self.month)
used = requests.aggregate(sum=models.Sum('accepted_sum'))["sum"]
return used/TOTAL_TAX if used else 0
def _get_next_date_db(dt=None, t='payment'):
if not dt:
dt = date.today()
if t == 'deadline':
qs = MonthlyData.objects.filter(deadline_dt__gte=dt)
else:
qs = MonthlyData.objects.filter(student_deadline_dt__gte=dt)
next_month = qs.order_by('payment_dt').first()
if next_month:
if t == 'payment':
return next_month.payment_dt
elif t == 'deadline':
return next_month.deadline_dt
elif t == 'student_deadline':
return next_month.student_deadline_dt
else:
raise ValueError
else:
return None
[docs]def get_next_date(dt=None, t='payment'):
next_date = _get_next_date_db(dt, t)
if not next_date:
next_date = _get_next_date_naive(dt, t)
return next_date
"""def sum_by_month(year, month):
requests = AidRequest.objects.filter(status=AidRequest.ACCEPTED, payment_dt__year=year, payment_dt__month=month)
return requests.aggregate(sum=models.Sum('accepted_sum'))["sum"]"""
[docs]class Scholarship(models.Model):
name = models.CharField("Название", max_length=100)
sum = models.FloatField("Сумма")
MONTH = 1
SEMESTER = 2
YEAR = 3
PAYMENT_FREQUENCY = (
(MONTH, "Раз в месяц"),
(SEMESTER, "Раз в семестр"),
(YEAR, "Раз в год"),
)
frequency = models.IntegerField("Частота выплат", choices=PAYMENT_FREQUENCY, default=MONTH)
class Meta:
verbose_name = "стипендия"
verbose_name_plural = "стипендии"
def __str__(self):
return self.name
[docs]class Scholar(models.Model):
student = models.ForeignKey(StudentInfo, verbose_name="Студент")
scholarship = models.ForeignKey(Scholarship, verbose_name="Стипендия")
class Meta:
verbose_name = "стипендиат"
verbose_name_plural = "стипендиаты"
def __str__(self):
return "{} ({})".format(self.scholarship, self.student)