Cloned DMOJ
This commit is contained in:
parent
f623974b58
commit
49dc9ff10c
513 changed files with 132349 additions and 39 deletions
204
judge/models/profile.py
Normal file
204
judge/models/profile.py
Normal file
|
@ -0,0 +1,204 @@
|
|||
from operator import mul
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from fernet_fields import EncryptedCharField
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
|
||||
from judge.models.choices import ACE_THEMES, MATH_ENGINES_CHOICES, TIMEZONE
|
||||
from judge.models.runtime import Language
|
||||
from judge.ratings import rating_class
|
||||
|
||||
__all__ = ['Organization', 'Profile', 'OrganizationRequest']
|
||||
|
||||
|
||||
class EncryptedNullCharField(EncryptedCharField):
|
||||
def get_prep_value(self, value):
|
||||
if not value:
|
||||
return None
|
||||
return super(EncryptedNullCharField, self).get_prep_value(value)
|
||||
|
||||
|
||||
class Organization(models.Model):
|
||||
name = models.CharField(max_length=128, verbose_name=_('organization title'))
|
||||
slug = models.SlugField(max_length=128, verbose_name=_('organization slug'),
|
||||
help_text=_('Organization name shown in URL'))
|
||||
short_name = models.CharField(max_length=20, verbose_name=_('short name'),
|
||||
help_text=_('Displayed beside user name during contests'))
|
||||
about = models.TextField(verbose_name=_('organization description'))
|
||||
registrant = models.ForeignKey('Profile', verbose_name=_('registrant'), on_delete=models.CASCADE,
|
||||
related_name='registrant+', help_text=_('User who registered this organization'))
|
||||
admins = models.ManyToManyField('Profile', verbose_name=_('administrators'), related_name='admin_of',
|
||||
help_text=_('Those who can edit this organization'))
|
||||
creation_date = models.DateTimeField(verbose_name=_('creation date'), auto_now_add=True)
|
||||
is_open = models.BooleanField(verbose_name=_('is open organization?'),
|
||||
help_text=_('Allow joining organization'), default=True)
|
||||
slots = models.IntegerField(verbose_name=_('maximum size'), null=True, blank=True,
|
||||
help_text=_('Maximum amount of users in this organization, '
|
||||
'only applicable to private organizations'))
|
||||
access_code = models.CharField(max_length=7, help_text=_('Student access code'),
|
||||
verbose_name=_('access code'), null=True, blank=True)
|
||||
logo_override_image = models.CharField(verbose_name=_('Logo override image'), default='', max_length=150,
|
||||
blank=True,
|
||||
help_text=_('This image will replace the default site logo for users '
|
||||
'viewing the organization.'))
|
||||
|
||||
def __contains__(self, item):
|
||||
if isinstance(item, int):
|
||||
return self.members.filter(id=item).exists()
|
||||
elif isinstance(item, Profile):
|
||||
return self.members.filter(id=item.id).exists()
|
||||
else:
|
||||
raise TypeError('Organization membership test must be Profile or primany key')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('organization_home', args=(self.id, self.slug))
|
||||
|
||||
def get_users_url(self):
|
||||
return reverse('organization_users', args=(self.id, self.slug))
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
permissions = (
|
||||
('organization_admin', 'Administer organizations'),
|
||||
('edit_all_organization', 'Edit all organizations'),
|
||||
)
|
||||
verbose_name = _('organization')
|
||||
verbose_name_plural = _('organizations')
|
||||
|
||||
|
||||
class Profile(models.Model):
|
||||
user = models.OneToOneField(User, verbose_name=_('user associated'), on_delete=models.CASCADE)
|
||||
about = models.TextField(verbose_name=_('self-description'), null=True, blank=True)
|
||||
timezone = models.CharField(max_length=50, verbose_name=_('location'), choices=TIMEZONE,
|
||||
default=settings.DEFAULT_USER_TIME_ZONE)
|
||||
language = models.ForeignKey('Language', verbose_name=_('preferred language'), on_delete=models.SET_DEFAULT,
|
||||
default=Language.get_default_language_pk)
|
||||
points = models.FloatField(default=0, db_index=True)
|
||||
performance_points = models.FloatField(default=0, db_index=True)
|
||||
problem_count = models.IntegerField(default=0, db_index=True)
|
||||
ace_theme = models.CharField(max_length=30, choices=ACE_THEMES, default='github')
|
||||
last_access = models.DateTimeField(verbose_name=_('last access time'), default=now)
|
||||
ip = models.GenericIPAddressField(verbose_name=_('last IP'), blank=True, null=True)
|
||||
organizations = SortedManyToManyField(Organization, verbose_name=_('organization'), blank=True,
|
||||
related_name='members', related_query_name='member')
|
||||
display_rank = models.CharField(max_length=10, default='user', verbose_name=_('display rank'),
|
||||
choices=(('user', 'Normal User'), ('setter', 'Problem Setter'), ('admin', 'Admin')))
|
||||
mute = models.BooleanField(verbose_name=_('comment mute'), help_text=_('Some users are at their best when silent.'),
|
||||
default=False)
|
||||
is_unlisted = models.BooleanField(verbose_name=_('unlisted user'), help_text=_('User will not be ranked.'),
|
||||
default=False)
|
||||
rating = models.IntegerField(null=True, default=None)
|
||||
user_script = models.TextField(verbose_name=_('user script'), default='', blank=True, max_length=65536,
|
||||
help_text=_('User-defined JavaScript for site customization.'))
|
||||
current_contest = models.OneToOneField('ContestParticipation', verbose_name=_('current contest'),
|
||||
null=True, blank=True, related_name='+', on_delete=models.SET_NULL)
|
||||
math_engine = models.CharField(verbose_name=_('math engine'), choices=MATH_ENGINES_CHOICES, max_length=4,
|
||||
default=settings.MATHOID_DEFAULT_TYPE,
|
||||
help_text=_('the rendering engine used to render math'))
|
||||
is_totp_enabled = models.BooleanField(verbose_name=_('2FA enabled'), default=False,
|
||||
help_text=_('check to enable TOTP-based two factor authentication'))
|
||||
totp_key = EncryptedNullCharField(max_length=32, null=True, blank=True, verbose_name=_('TOTP key'),
|
||||
help_text=_('32 character base32-encoded key for TOTP'),
|
||||
validators=[RegexValidator('^$|^[A-Z2-7]{32}$',
|
||||
_('TOTP key must be empty or base32'))])
|
||||
notes = models.TextField(verbose_name=_('internal notes'), null=True, blank=True,
|
||||
help_text=_('Notes for administrators regarding this user.'))
|
||||
|
||||
@cached_property
|
||||
def organization(self):
|
||||
# We do this to take advantage of prefetch_related
|
||||
orgs = self.organizations.all()
|
||||
return orgs[0] if orgs else None
|
||||
|
||||
@cached_property
|
||||
def username(self):
|
||||
return self.user.username
|
||||
|
||||
_pp_table = [pow(settings.DMOJ_PP_STEP, i) for i in range(settings.DMOJ_PP_ENTRIES)]
|
||||
|
||||
def calculate_points(self, table=_pp_table):
|
||||
from judge.models import Problem
|
||||
data = (Problem.objects.filter(submission__user=self, submission__points__isnull=False, is_public=True,
|
||||
is_organization_private=False)
|
||||
.annotate(max_points=Max('submission__points')).order_by('-max_points')
|
||||
.values_list('max_points', flat=True).filter(max_points__gt=0))
|
||||
extradata = Problem.objects.filter(submission__user=self, submission__result='AC', is_public=True) \
|
||||
.values('id').distinct().count()
|
||||
bonus_function = settings.DMOJ_PP_BONUS_FUNCTION
|
||||
points = sum(data)
|
||||
problems = len(data)
|
||||
entries = min(len(data), len(table))
|
||||
pp = sum(map(mul, table[:entries], data[:entries])) + bonus_function(extradata)
|
||||
if self.points != points or problems != self.problem_count or self.performance_points != pp:
|
||||
self.points = points
|
||||
self.problem_count = problems
|
||||
self.performance_points = pp
|
||||
self.save(update_fields=['points', 'problem_count', 'performance_points'])
|
||||
return points
|
||||
|
||||
calculate_points.alters_data = True
|
||||
|
||||
def remove_contest(self):
|
||||
self.current_contest = None
|
||||
self.save()
|
||||
|
||||
remove_contest.alters_data = True
|
||||
|
||||
def update_contest(self):
|
||||
contest = self.current_contest
|
||||
if contest is not None and (contest.ended or not contest.contest.is_accessible_by(self.user)):
|
||||
self.remove_contest()
|
||||
|
||||
update_contest.alters_data = True
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('user_page', args=(self.user.username,))
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
||||
@classmethod
|
||||
def get_user_css_class(cls, display_rank, rating, rating_colors=settings.DMOJ_RATING_COLORS):
|
||||
if rating_colors:
|
||||
return 'rating %s %s' % (rating_class(rating) if rating is not None else 'rate-none', display_rank)
|
||||
return display_rank
|
||||
|
||||
@cached_property
|
||||
def css_class(self):
|
||||
return self.get_user_css_class(self.display_rank, self.rating)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('test_site', 'Shows in-progress development stuff'),
|
||||
('totp', 'Edit TOTP settings'),
|
||||
)
|
||||
verbose_name = _('user profile')
|
||||
verbose_name_plural = _('user profiles')
|
||||
|
||||
|
||||
class OrganizationRequest(models.Model):
|
||||
user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='requests', on_delete=models.CASCADE)
|
||||
organization = models.ForeignKey(Organization, verbose_name=_('organization'), related_name='requests',
|
||||
on_delete=models.CASCADE)
|
||||
time = models.DateTimeField(verbose_name=_('request time'), auto_now_add=True)
|
||||
state = models.CharField(max_length=1, verbose_name=_('state'), choices=(
|
||||
('P', 'Pending'),
|
||||
('A', 'Approved'),
|
||||
('R', 'Rejected'),
|
||||
))
|
||||
reason = models.TextField(verbose_name=_('reason'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('organization join request')
|
||||
verbose_name_plural = _('organization join requests')
|
Loading…
Add table
Add a link
Reference in a new issue