class Order(models.Model): ORDER_STATUS_OPENED = 1 ORDER_STATUS_CLOSED = 2 ORDER_STATUS_CHOICES = ( (ORDER_STATUS_OPENED, 'Opened'), (ORDER_STATUS_CLOSED, 'Closed'), ) ORDER_TYPE_BUY = 1 ORDER_TYPE_SELL = 2 ORDER_TYPE_CHOICES = ( (ORDER_TYPE_BUY, 'buy'), (ORDER_TYPE_SELL, 'sell'), ) user = models.ForeignKey(SystemUser) currency_pair = models.ForeignKey(CurrencyPair) status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES) type = models.SmallIntegerField(choices=ORDER_TYPE_CHOICES) start_time = models.DateTimeField(auto_now_add=True) start_value = models.ForeignKey(CurrencyPairValue, related_name='start_value') end_time = models.DateTimeField(null=True) end_value = models.ForeignKey(CurrencyPairValue, related_name='end_value', null=True) amount = models.FloatField(validators=[validators.MinValueValidator(0.01)]) end_profit = models.FloatField(null=True, blank=True) def close(self): self.end_time = timezone.now() end_value = CurrencyPairValue.objects.filter(currency_pair=self.currency_pair).first() self.end_value = end_value self.status = Order.ORDER_STATUS_CLOSED with transaction.atomic(): amount = self.get_profit(end_value) self.user.account.change_amount_after_order(amount, raise_exception=False) self.end_profit = amount self.save() def get_profit(self, currency_pair_value): amount = self.amount if self.type == self.ORDER_TYPE_BUY: debt_amount = amount * self.start_value.ask amount -= debt_amount / currency_pair_value.bid rest_currency = self.currency_pair.base_currency else: amount *= (self.start_value.bid - currency_pair_value.ask) rest_currency = self.currency_pair.quoted_currency user_currency = self.user.account.currency if rest_currency != user_currency: sub_pair = CurrencyPair.objects.get( Q(base_currency=rest_currency, quoted_currency=user_currency) | Q(base_currency=user_currency, quoted_currency=rest_currency) ) sub_pair = CurrencyPairValue.objects.filter(currency_pair=sub_pair).first() if sub_pair.currency_pair.base_currency == rest_currency: amount *= sub_pair.bid else: amount /= sub_pair.ask return amount def __unicode__(self): return '%s [%s]: %s' % (self.get_type_display(), self.get_status_display(), self.start_time)
class SupportContract(Contract): """Support contract model.""" FIXED_FEE_PERIOD = Choices( ('DAILY', _('Daily')), ('WEEKLY', _('Weekly')), ('MONTHLY', _('Monthly')), ('YEARLY', _('Yearly')), ) day_rate = models.DecimalField(blank=True, null=True, max_digits=6, decimal_places=2, default=0.00, validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(9999), ]) fixed_fee = models.DecimalField(blank=True, null=True, max_digits=9, decimal_places=2, default=0.00, validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(9999999), ]) fixed_fee_period = models.CharField(blank=True, null=True, max_length=10, choices=FIXED_FEE_PERIOD) starts_at = models.DateField() ends_at = models.DateField(blank=True, null=True) @classmethod def perform_additional_validation(cls, data, instance=None): """Perform additional validation on the object.""" instance_id = instance.id if instance else None # noqa starts_at = data.get('starts_at', getattr(instance, 'starts_at', None)) ends_at = data.get('ends_at', getattr(instance, 'ends_at', None)) day_rate = data.get('day_rate', getattr(instance, 'day_rate', None)) fixed_fee = data.get('fixed_fee', getattr(instance, 'fixed_fee', None)) fixed_fee_period = data.get( 'fixed_fee_period', getattr(instance, 'fixed_fee_period', None)) if starts_at and ends_at: # Verify whether the start date of the contract comes before the end date if starts_at >= ends_at: raise ValidationError( _('The start date should be set before the end date'), ) # Ensure we have either a day rate or a fixed fee + period, but never both if day_rate: if fixed_fee: raise ValidationError( _('A contract can not have both a fixed fee and a day rate' ), ) elif fixed_fee: if not fixed_fee_period: raise ValidationError( _('A contract with a fixed fee requires a fixed fee period' ), ) else: raise ValidationError( _('A contract should have either a fixed fee or a day rate'), ) def get_validation_args(self): """Get a dict used for validation based on this instance.""" return { 'starts_at': getattr(self, 'starts_at', None), 'ends_at': getattr(self, 'ends_at', None), 'day_rate': getattr(self, 'day_rate', None), 'fixed_fee': getattr(self, 'fixed_fee', None), 'fixed_fee_period': getattr(self, 'fixed_fee_period', None), }
class Application(models.Model): GENDER_CHOICES = [ (None, ""), ("male", "Male"), ("female", "Female"), ("non-binary", "Non-binary"), ("other", "Other"), ("no-answer", "Prefer not to answer"), ] ETHNICITY_CHOICES = [ (None, ""), ("american-native", "American Indian or Alaskan Native"), ("asian-pacific-islander", "Asian / Pacific Islander"), ("black-african-american", "Black or African American"), ("hispanic", "Hispanic"), ("caucasian", "White / Caucasian"), ("other", "Multiple ethnicity / Other"), ("no-answer", "Prefer not to answer"), ] STUDY_LEVEL_CHOICES = [ (None, ""), ("undergraduate", "Undergraduate"), ("gradschool", "Graduate School"), ] user = models.OneToOneField(User, on_delete=models.CASCADE, null=False) team = models.ForeignKey( Team, related_name="applications", on_delete=models.CASCADE, null=False ) # User Submitted Fields birthday = models.DateField( null=False, validators=[ validators.MaxValueValidator( date(2003, 2, 6), message="You must be over 18 years old on February 6, 2021 to participate in MakeUofT.", ) ], ) gender = models.CharField(max_length=50, choices=GENDER_CHOICES, null=False) ethnicity = models.CharField(max_length=50, choices=ETHNICITY_CHOICES, null=False) school = models.CharField(help_text="University", max_length=255, null=False,) study_level = models.CharField( max_length=50, choices=STUDY_LEVEL_CHOICES, null=False ) graduation_year = models.IntegerField( null=False, validators=[ validators.MinValueValidator( 2000, message="Enter a realistic graduation year." ), validators.MaxValueValidator( 2030, message="Enter a realistic graduation year." ), ], ) resume = models.FileField( upload_to="applications/resumes/", validators=[ UploadedFileValidator( content_types=["application/pdf"], max_upload_size=20 * 1024 * 1024 ) ], null=False, ) resume_sharing = models.BooleanField( help_text="I consent to IEEE UofT sharing my resume with event sponsors (optional).", default=False, null=False, ) eligibility_agree = models.BooleanField( help_text="I confirm that I will be over 18 years old and a university student " "on February 6, 2021.", blank=False, null=False, ) conduct_agree = models.BooleanField( help_text="I have read and agree to the " '<a href="https://docs.google.com/document/d/1RH36R1nt8KQfKtd2YoNAJNuaCW5um55a6oVP_bWRK6U/edit" target="_blank">code of conduct</a>.', blank=False, null=False, ) data_agree = models.BooleanField( help_text="I consent to have the data in this application collected for event purposes " "including administration, ranking, and event communication.", blank=False, null=False, ) rsvp = models.BooleanField(null=True) created_at = models.DateTimeField(auto_now_add=True, null=False) updated_at = models.DateTimeField(auto_now=True, null=False) def __str__(self): return f"{self.user.first_name} {self.user.last_name}"
class MRSRequest(models.Model): SESSION_KEY = 'MRSRequest.ids' STATUS_NEW = 1 # matches admin.models.ADDITION # Those have status different from admin flags STATUS_CANCELED = 100 STATUS_REJECTED = 999 STATUS_INPROGRESS = 1000 STATUS_VALIDATED = 2000 STATUS_CHOICES = ( (STATUS_NEW, 'Soumise'), (STATUS_CANCELED, 'Annulée'), (STATUS_REJECTED, 'Rejetée'), (STATUS_INPROGRESS, 'En cours de liquidation'), (STATUS_VALIDATED, 'Validée'), ) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) creation_datetime = models.DateTimeField( default=timezone.now, db_index=True, verbose_name='Date et heure de la demande', ) display_id = models.BigIntegerField( verbose_name='Numéro de demande', unique=True, ) caisse = models.ForeignKey( 'caisse.Caisse', on_delete=models.SET_NULL, null=True, ) insured = models.ForeignKey( 'person.Person', on_delete=models.SET_NULL, null=True, ) insured_shift = models.NullBooleanField( default=None, null=True, blank=True, verbose_name='Assuré a basculé sur cette demande', ) modevp = models.BooleanField( default=False, blank=True, verbose_name='Avez vous voyagé en véhicule personnel ?', help_text='(Voiture, moto)', ) distancevp = models.PositiveIntegerField( verbose_name='Distance (km)', help_text='Indiquez le nombre total de kilomètres parcourus :' ' Par exemple, vous réalisez 2 trajets de 40 kilomètres' ' aller/retour : déclarez 80 kilomètres parcourus.', null=True, blank=True, ) expensevp_toll = models.DecimalField( null=True, blank=True, decimal_places=2, max_digits=6, validators=[validators.MinValueValidator(Decimal('0.00'))], verbose_name='Frais de péage', help_text='Somme totale des frais de péage (en € TTC)', ) expensevp_parking = models.DecimalField( null=True, blank=True, decimal_places=2, max_digits=6, validators=[validators.MinValueValidator(Decimal('0.00'))], verbose_name='Frais de stationnement', help_text='Somme totale des frais de stationnement (en € TTC)', ) @denormalized( models.DecimalField, blank=True, null=True, decimal_places=2, max_digits=6, validators=[validators.MinValueValidator(Decimal('0.00'))], verbose_name='Total des frais', help_text='Somme des frais de péage et stationnement (en € TTC)' ) def expensevp(self): if self.expensevp: return self.expensevp expensevp_parking = self.expensevp_parking or 0 expensevp_toll = self.expensevp_toll or 0 return expensevp_parking + expensevp_toll modeatp = models.BooleanField( blank=True, default=False, verbose_name='Avez vous voyagé en transports en commun ?', help_text='(Avion, bus, métro, train, bateau…)', ) expenseatp = models.DecimalField( blank=True, null=True, decimal_places=2, max_digits=6, default=0, validators=[validators.MinValueValidator(Decimal('0.00'))], verbose_name='Frais de transports', help_text=( 'Somme totale des frais de' ' transport en commun (en € TTC)' ) ) pel = models.CharField( max_length=14, verbose_name='Numéro de PMET', null=True, blank=True, validators=[ validators.RegexValidator( '[a-zA-Z0-9]{14}', message='Le numéro de PMET doit comporter' ' 14 caractères alpha numériques', ) ], ) status = models.IntegerField( choices=STATUS_CHOICES, verbose_name='Statut', default=STATUS_NEW, ) status_datetime = models.DateTimeField( db_index=True, null=True, blank=True, verbose_name='Date et heure de changement de statut', ) status_user = models.ForeignKey( settings.AUTH_USER_MODEL, db_index=True, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='Auteur du changement de statut', ) suspended = models.BooleanField( db_index=True, blank=True, default=False ) institution = models.ForeignKey( 'institution.Institution', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='Établissement', ) mandate_datevp = models.DateField( null=True, blank=True, verbose_name='Date de mandatement VP', validators=[ validators.MinValueValidator( datetime.date(year=2000, month=1, day=1) ) ], ) mandate_dateatp = models.DateField( null=True, blank=True, verbose_name='Date de mandatement ATP', validators=[ validators.MinValueValidator( datetime.date(year=2000, month=1, day=1) ) ], ) payment_base = models.DecimalField( null=True, blank=True, max_digits=8, decimal_places=2, verbose_name='Base de remboursement', ) payment_amount = models.DecimalField( null=True, blank=True, max_digits=8, decimal_places=2, verbose_name='Montant remboursé', ) adeli = models.IntegerField(null=True, blank=True) data = JSONField( blank=True, encoder=DjangoJSONEncoder, null=True, verbose_name='Formulaire tel que soumit par l\'usager', ) conflicts_accepted = models.PositiveIntegerField( default=0, verbose_name='Nb. signalements acceptés', help_text='Nombre de signalements acceptés pour cette demande', ) conflicts_resolved = models.PositiveIntegerField( default=0, verbose_name='Nb. signalements résolus', help_text='Nombre de signalements résolus avant soumission', ) token = models.CharField( default=secrets.token_urlsafe, null=True, editable=False, verbose_name='Token d\'authentification pour modifier la demande', max_length=255, ) objects = MRSRequestManager() class Meta: verbose_name = 'Demande' ordering = ['-creation_datetime'] @property def dates(self): if getattr(self, '_dates', None) is None: self._dates = set() for transport in self.transport_set.all(): for date in transport.dates: self._dates.add(date) self._dates = sorted(self._dates) return self._dates @property def duplicate_transports(self): if getattr(self, '_duplicate_transports', None) is None: self._duplicate_transports = Transport.objects.filter( mrsrequest__insured=self.insured, mrsrequest__status__in=( self.STATUS_INPROGRESS, self.STATUS_VALIDATED, ), ).exclude( models.Q(mrsrequest__pk=self.pk) ).filter( models.Q(date_depart__in=self.dates) | models.Q(date_return__in=self.dates) ).distinct().order_by( 'mrsrequest__creation_datetime' ).select_related( 'mrsrequest' ).prefetch_related('mrsrequest__transport_set') return self._duplicate_transports @property def duplicates_by_transport(self): if getattr(self, '_duplicates_dates', None) is None: dupes = dict() for date in self.dates: for transport in self.duplicate_transports: if date in transport.dates: dupes.setdefault(transport.mrsrequest, set()) dupes[transport.mrsrequest].add(date) self._duplicates_dates = collections.OrderedDict() for key in sorted(dupes.keys(), key=lambda x: x.creation_datetime): self._duplicates_dates[key] = sorted(dupes[key]) return self._duplicates_dates def __str__(self): return str(self.display_id) @property def modes(self): modes = [] for mode in ['atp', 'vp']: if getattr(self, f'mode{mode}', None): modes.append(mode) return modes def status_in(self, *names): return self.status in [ getattr(self, f'STATUS_{name.upper()}') for name in names ] # todo: rename to status_update def update_status(self, user, status, log_datetime=None, create_logentry=False): self.status = MRSRequest.get_status_id(status) self.status_datetime = log_datetime or timezone.now() self.status_user = user self.save() if not create_logentry: return self.logentries.create( datetime=self.status_datetime, user=self.status_user, comment=self.get_status_display(), action=self.status ) @classmethod def get_status_label(cls, number): for flag, label in cls.STATUS_CHOICES: if flag == number: return label @classmethod def get_status_id(cls, name): if isinstance(name, int): return name return getattr(cls, 'STATUS_{}'.format(name.upper())) def denorm_reset(self): self.delay = self.cost = self.saving = None @denormalized( models.DecimalField, decimal_places=2, max_digits=10, null=True, ) def taxi_cost(self): transport = self.transport_set.first() num = 1 if transport and not transport.date_return else 2 return Decimal( ( ((self.distancevp or 0) * 1.62) + (1.9 * num * self.transport_set.count()) ) * 0.91 ).quantize(TWOPLACES) def field_changed(self, fieldname): """ If the field was changed, return its original value. """ # The oldest logentry has the original value. if not hasattr(self, '_logentries'): self._logentries = self.logentries.order_by('datetime') for entry in self._logentries: if entry.data and \ 'changed' in entry.data and \ fieldname in entry.data['changed']: val = entry.data['changed'][fieldname][0] return val return False @denormalized( models.DecimalField, decimal_places=2, max_digits=8, null=True, verbose_name='économie', ) def saving(self): if not self.insured or not self.insured.shifted: return 0 if not self.modevp or not self.payment_base: return return Decimal( float(self.taxi_cost) - float(self.payment_base) ).quantize(TWOPLACES) @denormalized( models.DecimalField, decimal_places=2, max_digits=5, null=True, ) def delay(self): if not self.mandate_date: return mandate_datetime = datetime.datetime( self.mandate_date.year, self.mandate_date.month, self.mandate_date.day, 0, tzinfo=pytz.timezone(settings.TIME_ZONE), ) delta = mandate_datetime - self.creation_datetime_normalized return delta.days + (delta.seconds / 60 / 60 / 24) @property def status_days(self): return (timezone.now() - self.creation_datetime_normalized).days @property def days(self): return (timezone.now() - self.creation_datetime_normalized).days @property def creation_day_time(self): # french calendar date and tz time for creation_datetime return self.creation_datetime.astimezone( pytz.timezone(settings.TIME_ZONE) ) @property def creation_day(self): # french calendar date for creation_datetime return self.creation_day_time.date() @property def waiting(self): return self.status not in ( self.STATUS_VALIDATED, self.STATUS_REJECTED ) @property def tooltip(self): if self.waiting: if self.days: return 'En attente de traitement depuis {} jours'.format( self.days ) else: return 'En attente de traitement depuis aujourd\'hui' return 'Traité' @property def color(self): if not self.waiting: return '' if self.days >= 6: return 'red' elif self.days >= 4: return 'orange' return '' @property def estimate(self): result = 0 if self.distancevp: result += self.distancevp * 0.3 if self.expensevp: result += float(self.expensevp) if self.expenseatp: result += float(self.expenseatp) return '%.2f' % result def is_allowed(self, request): return str(self.id) in request.session.get(self.SESSION_KEY, {}) def allow(self, request): if self.SESSION_KEY not in request.session: request.session[self.SESSION_KEY] = {} request.session[self.SESSION_KEY].setdefault(str(self.id), dict()) # The above doesn't use the request.session setter, won't automatically # trigger session save unless we do the following request.session.modified = True def save_bills(self): Bill.objects.recorded_uploads(self.id).update(mrsrequest=self) def delete_pmt(self): PMT.objects.recorded_uploads(self.id).delete() def save_pmt(self): PMT.objects.recorded_uploads(self.id).update(mrsrequest=self) def get_bills(self, mode=None): bills = getattr(self, '_bills', None) if not bills: self._bills = bills = self.bill_set.all() if not mode: return bills return [i for i in bills if i.mode == mode] # shortcuts to the above, for stupid django templates @property def billatps(self): return self.get_bills('atp') @property def billvps(self): return self.get_bills('vp') @property def total_size(self): if getattr(self, '_total_size', None) is None: self._total_size = sum( [ len(b.binary) for b in [*self.pmt_set.all()] + [*self.bill_set.all()] ] ) return self._total_size def get_admin_url(self): return reverse('admin:mrsrequest_mrsrequest_change', args=[self.pk]) def get_reject_url(self): return reverse('mrsrequest:reject', args=[self.pk]) def get_validate_url(self): return reverse('mrsrequest:validate', args=[self.pk]) def get_cancel_url(self): return reverse('demande-cancel', args=[self.pk, self.token]) def get_update_url(self): return reverse('demande-update', args=[self.pk, self.token]) @property def creation_date_normalized(self): return pytz.timezone(settings.TIME_ZONE).normalize( self.creation_datetime).strftime('%d/%m/%Y') @property def creation_datetime_normalized(self): return pytz.timezone(settings.TIME_ZONE).normalize( self.creation_datetime) @property def inprogress_day_number(self): event = self.logentries.filter(status=self.STATUS_INPROGRESS).first() if not event: return 0 dt = pytz.timezone(settings.TIME_ZONE).normalize(event.datetime) return '{:03d}'.format(dt.timetuple().tm_yday) @property def order_number(self): previous = type(self).objects.filter( insured=self.insured, creation_datetime__gte=datetime_min(self.creation_datetime), creation_datetime__lte=self.creation_datetime, ) if self.pk: previous = previous.exclude(pk=self.pk) number = previous.count() + 1 if number > 99: return '99' return '{:02d}'.format(number) @property def mandate_date(self): dates = (self.mandate_datevp, self.mandate_dateatp) if dates[0] and dates[1]: return dates[0] if dates[0] > dates[1] else dates[1] for date in dates: if date: return date def make_display_id(self): normalized = pytz.timezone(settings.TIME_ZONE).normalize( self.creation_datetime) prefix = normalized.strftime('%Y%m%d') last = MRSRequest.objects.filter( display_id__startswith=prefix, ).order_by('display_id').last() number = 0 last_display_id = getattr(last, 'display_id', None) if last_display_id and len(str(last_display_id)) == 12: number = int(str(last_display_id)[-4:]) + 1 return int('{}{:04d}'.format(prefix, number)) def save(self, *args, **kwargs): """ Unfortunate display_id conflict handling. Despite our recommendations, product owner decided to generate ids according to rules which are victim of conflicts. At the beginning it was not a problem, but now that there are concurrent users on the platform it's of course a problem. Please forgive the horrible palliative fix that you are about to witness. """ duplicate_display_id = 'duplicate key value violates unique constraint "mrsrequest_mrsrequest_display_id_key"' # noqa if not self.creation_datetime: self.creation_datetime = timezone.now() if not self.display_id: self.display_id = self.make_display_id() tries = 100 while tries: try: with transaction.atomic(): return super().save(*args, **kwargs) except IntegrityError as exc: # swallow duplicate "insane id" generation if not exc.args[0].startswith(duplicate_display_id): raise if not tries: raise self.display_id = self.display_id + 1 tries -= 1
class Item(models.Model): """ データ定義クラス 各フィールドを定義する 参考: ・公式 モデルフィールドリファレンス https://docs.djangoproject.com/ja/2.1/ref/models/fields/ """ # 名前 customer_name = models.CharField( verbose_name='名前', max_length=16, blank=True, null=True, ) # カナ customer_kana = models.CharField( verbose_name='カナ', max_length=16, blank=True, null=True, ) # 郵便番号 customer_post_code = models.IntegerField( verbose_name='郵便番号', blank=True, null=True, default=0, validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(9999999) ]) # 住所 customer_address = models.CharField( verbose_name='住所', max_length=64, blank=True, null=True, ) # 電話番号 customer_tel_number = models.CharField( verbose_name='電話番号', max_length=14, blank=True, null=True, ) # メモ customer_memo = models.TextField( verbose_name='メモ', blank=True, null=True, ) # タイムスタンプ customer_timestamp = models.DateTimeField( verbose_name='タイムスタンプ', auto_now=True # 登録時と更新時に現在時間を設定 ) # 以下、管理項目 # 作成者(ユーザー) created_by = models.ForeignKey( User, verbose_name='作成者', blank=True, null=True, related_name='CreatedBy', on_delete=models.SET_NULL, editable=False, ) # 作成時間 created_at = models.DateTimeField( verbose_name='作成時間', blank=True, null=True, editable=False, ) # 更新者(ユーザー) updated_by = models.ForeignKey( User, verbose_name='更新者', blank=True, null=True, related_name='UpdatedBy', on_delete=models.SET_NULL, editable=False, ) # 更新時間 updated_at = models.DateTimeField( verbose_name='更新時間', blank=True, null=True, editable=False, ) # def __str__(self): # """ # リストボックスや管理画面での表示 # """ # return self.sample_1 def __str__(self): return self.customer_name class Meta: """ 管理画面でのタイトル表示 """ verbose_name = verbose_name_plural = '顧客'
class Persona(models.Model): identificacion = models.CharField(max_length=20, unique=True) tipo_documento_identificacion = models.CharField( max_length=30, choices=constants.TIPO_DOCUMENTO) nombres = models.CharField(max_length=100) apellidos = models.CharField(max_length=100) sexo = models.CharField(max_length=12, choices=constants.SEXO) estado_civil = models.CharField(max_length=20, choices=constants.ESTADO_CIVIL) edad = models.PositiveIntegerField(validators=[ validators.MinValueValidator(1), validators.MaxValueValidator(100) ]) telefono_1 = models.CharField(max_length=12, blank=True, null=True) telefono_2 = models.CharField(max_length=12, blank=True, null=True) telefono_3 = models.CharField(max_length=12, blank=True, null=True) fecha_de_registro = models.DateField(auto_now_add=True) fecha_ingreso = models.DateField() tiene_empleo = models.BooleanField(default=False) origen_ingreso = models.CharField(max_length=50) ingreso_promedio_mensual = models.PositiveIntegerField(blank=True, null=True) ingreso_promedio_familiares = models.PositiveIntegerField(blank=True, null=True) ingreso_promedio_mensuales = models.PositiveIntegerField(blank=True, null=True) vulnerabilidad = models.BooleanField(default=False) hoja_de_vida = models.FileField(upload_to='cv', blank=True, null=True) cv_last_update = models.DateField(blank=True, null=True, editable=False) email = models.EmailField(blank=True, null=True) # educacion grado_escolaridad = models.ForeignKey(GradoEscolaridad) titulo_grado = ChainedForeignKey(TituloGrado, chained_field='grado_escolaridad', chained_model_field='grado_escolaridad') tipo_vivienda = models.ForeignKey(TipoVivienda) tipo_manzana = models.ForeignKey(TipoManzana) manzana = ChainedForeignKey(Manzana, chained_field='tipo_manzana', chained_model_field='tipo_manzana') casa = ChainedForeignKey(Casa, chained_field='manzana', chained_model_field='numero_manzana') def __str__(self): return '{} - {} : {} '.format(self.get_full_name(), self.identificacion, self.get_vivienda()) def get_update_url(self): return reverse_lazy('personas:editar_persona', kwargs={'pk': self.pk}) def get_full_name(self): return '{} {}'.format(self.nombres, self.apellidos) def get_vivienda(self): return self.casa def get_absolute_url(self): return reverse_lazy('personas:detalle_persona', kwargs={'pk': self.ppk}) def get_experiencias_laborales(self): return ExperienciaLaboralPersona.objects.filter(persona=self) def get_formaciones_complementarias(self): return FormacionComplementariaPersona.objects.filter(persona=self) def get_habilidades_blandas(self): return HabilidadBlanda.objects.filter(persona=self) def get_formaciones_trabajo(self): return FormacionTrabajoPersona.objects.filter(persona=self) def get_vacantes(self): return VacantePersona.objects.filter(persona=self) def get_negocios(self): return Negocio.objects.filter(propietario=self) def get_emprendimientos(self): return Emprendimiento.objects.filter(persona=self)
class MissionJudgeFeedback(models.Model): """Stores feedback from judges on a team's mission performance.""" # The mission for which this is feedback. mission = models.ForeignKey(MissionConfig, on_delete=models.CASCADE) # The user for which this is feedback. user = models.ForeignKey(settings.AUTH_USER_MODEL) # Time spent occupying runway and airspace. flight_time = models.DurationField() # Time spent handling data on mission clock. post_process_time = models.DurationField() # Whether the team used their single timeout. used_timeout = models.BooleanField() # Whether the team had the min auto flight time. min_auto_flight_time = models.BooleanField() # The number of times the pilot took over. safety_pilot_takeovers = models.IntegerField(validators=[ validators.MinValueValidator(0), ]) # Number of waypoints that were captured. waypoints_captured = models.IntegerField(validators=[ validators.MinValueValidator(0), ]) # Number of times the UAS went out of bounds. out_of_bounds = models.IntegerField(validators=[ validators.MinValueValidator(0), ]) # Number of times out of bounds compromised safety. unsafe_out_of_bounds = models.IntegerField(validators=[ validators.MinValueValidator(0), ]) # Whether something fell off UAS during flight. things_fell_off_uas = models.BooleanField() # Whether the UAS crashed. crashed = models.BooleanField() # Accuracy of drop in feet. air_drop_accuracy = models.IntegerField( choices=pb_utils.FieldChoicesFromEnum( interop_admin_api_pb2.MissionJudgeFeedback.AirDropAccuracy)) # Whether the UGV drove to the specified location. ugv_drove_to_location = models.BooleanField() # Grade of team performance [0, 100]. operational_excellence_percent = models.FloatField(validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(100), ]) class Meta: unique_together = (('mission', 'user'), ) def proto(self): """Get the proto formatted feedback.""" feedback = interop_admin_api_pb2.MissionJudgeFeedback() feedback.flight_time_sec = self.flight_time.total_seconds() feedback.post_process_time_sec = self.post_process_time.total_seconds() feedback.used_timeout = self.used_timeout feedback.min_auto_flight_time = self.min_auto_flight_time feedback.safety_pilot_takeovers = self.safety_pilot_takeovers feedback.waypoints_captured = self.waypoints_captured feedback.out_of_bounds = self.out_of_bounds feedback.unsafe_out_of_bounds = self.unsafe_out_of_bounds feedback.things_fell_off_uas = self.things_fell_off_uas feedback.crashed = self.crashed feedback.air_drop_accuracy = self.air_drop_accuracy feedback.ugv_drove_to_location = self.ugv_drove_to_location feedback.operational_excellence_percent = self.operational_excellence_percent return feedback
class EducationalModule(models.Model): STATUSES = ( (HIDDEN, _('Скрыт')), (DIRECT, _('Доступ по ссылке')), (PUBLISHED, _('Опубликован')), ) code = models.SlugField(verbose_name=_('Код'), unique=True) title = models.CharField(verbose_name=_('Название'), max_length=200) status = models.CharField(_('Статус'), max_length=16, choices=STATUSES, default='hidden') courses = SortedManyToManyField(Course, verbose_name=_('Курсы'), related_name='education_modules') cover = models.ImageField( _('Обложка EM'), upload_to='edmodule_cover', blank=True, help_text= _('Минимум {0}*{1}, картинки большего размера будут сжаты до этого размера' ).format(*getattr(settings, 'EDMODULE_COVER_IMAGE_SIZE', DEFAULT_COVER_SIZE))) about = models.TextField(verbose_name=_('Описание'), blank=False) price = models.IntegerField(verbose_name=_('Стоимость'), blank=True, null=True) discount = models.IntegerField(verbose_name=_('Скидка'), blank=True, default=0, validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(100) ]) vacancies = models.TextField(verbose_name=_('Вакансии'), blank=True, default='', help_text=_('HTML блок')) subtitle = models.TextField( verbose_name=_('Подзаголовок'), blank=True, default='', help_text=_('от 1 до 3 элементов, каждый с новой строки')) sum_ratings = models.PositiveIntegerField(verbose_name=_('Сумма оценок'), default=0) count_ratings = models.PositiveIntegerField( verbose_name=_('Количество оценок'), default=0) class Meta: verbose_name = _('Образовательный модуль') verbose_name_plural = _('Образовательные модули') def __str__(self): return '%s - %s' % (self.code, ', '.join( self.courses.values_list('slug', flat=True))) @cached_property def duration(self): """ сумма длительностей курсов (в неделях) """ duration = 0 for c, s in self.courses_with_closest_sessions: d = s.get_duration() if s else c.duration if not d: return 0 duration += d return duration @cached_property def whole_work(self): work = 0 for c, s in self.courses_with_closest_sessions: if s: w = (s.get_duration() or 0) * (s.get_workload() or 0) else: w = (c.duration or 0) * (c.workload or 0) if not w: return 0 work += w return work @property def workload(self): work = self.whole_work duration = self.duration if self.duration: return int(round(float(work) / duration, 0)) return 0 @property def instructors(self): """ объединение множества преподавателей всех курсов модуля упорядочивание по частоте вхождения в сессии, на которые мы записываем пользователя """ d = {} for c in self.courses.all(): if c.next_session: for i in c.next_session.get_instructors(): d[i] = d.get(i, 0) + 1 else: for i in c.instructor.all(): d[i] = d.get(i, 0) + 1 result = sorted(list(d.items()), key=lambda x: x[1], reverse=True) return [i[0] for i in result] @property def categories(self): return self._get_sorted('categories') def get_authors(self): return self._get_sorted('authors') def get_partners(self): return self._get_sorted('partners') def get_authors_and_partners(self): result = [] for i in self.get_authors() + self.get_partners(): if not i in result: result.append(i) return result def _get_sorted(self, attr): """ Возвращает список элементов attr отсортированный по количеству курсов, в которых этот attr встречается. Используется, например, для списка категорий модуля, которые отстортированы по количеству курсов, в которых они встречаются """ d = {} for c in self.courses_extended.prefetch_related(attr): for item in getattr(c, attr).all(): d[item] = d.get(item, 0) + 1 result = sorted(list(d.items()), key=lambda x: x[1], reverse=True) return [i[0] for i in result] def get_schedule(self): """ список тем """ schedule = [] all_courses = self.courses.values_list('id', flat=True) for c in self.courses_extended.prefetch_related('course'): if c.course.id not in all_courses: schedule.append({ 'course': { 'title': c.course.title }, 'schedule': '' }) else: schedule.append({ 'course': { 'title': c.course.title }, 'schedule': c.themes }) return schedule def get_rating(self): if self.count_ratings: return round(float(self.sum_ratings) / self.count_ratings, 2) return 0 def get_related(self): """ получение похожих курсов и специализаций (от 0 до 2) """ categories = self.categories if not categories: return [] modules = EducationalModule.objects.exclude(id=self.id).filter( courses__extended_params__categories__in=categories, status='published').distinct() courses = EdmoduleCourse.objects.exclude( id__in=self.courses.values_list('id', flat=True)).filter( extended_params__categories__in=categories, status='published').distinct() related = [] if modules: related.append({ 'type': 'em', 'item': random.sample(list(modules), 1)[0] }) if courses: sample = random.sample(list(courses), min(len(courses), 2)) for i in range(2 - len(related)): try: related.append({'type': 'course', 'item': sample[i]}) except IndexError: pass return related def get_sessions(self): """ хелпер для выбора сессий """ return [i.next_session for i in self.courses.all()] @cached_property def courses_extended(self): """ CourseExtendedParameters всех курсов модуля """ return CourseExtendedParameters.objects.filter( course__id__in=self.courses.values_list('id', flat=True)) def get_module_profit(self): """ для блока "что я получу в итоге" """ data = [] for c in self.courses_extended: if c.profit: data.extend(c.profit.splitlines()) data = [i.strip() for i in data if i.strip()] return list(set(data)) def get_requirements(self): try: s = self.extended_params.requirements or '' return [i.strip() for i in s.splitlines() if i.strip()] except: pass def get_price_list(self, for_user=None): """ :return: { 'courses': [(курс(Course), цена(int), ...], 'price': цена без скидок (int), 'whole_price': цена со скидкой (float), 'discount': скидка (int) } """ courses = self.courses.all() # берем цену ближайшей сессии, на которую можно записаться, или предыдущей session_for_course = {} now = timezone.now() course_paid = [] if for_user and for_user.is_authenticated: # если пользователь платил за какую-то сессию курса и успешно ее окончил или она # еще не завершилась, цена курса для него 0 reasons = EnrollmentReason.objects.filter( participant__user=for_user, session_enrollment_type__mode='verified').select_related( 'participant', 'participant__session') payment_for_course = defaultdict(list) for r in reasons: payment_for_course[r.participant.session.course_id].append(r) for course_id, payments in payment_for_course.items(): should_pay = True for r in payments: if r.participant.is_graduate: should_pay = False break if r.participant.session.datetime_ends and r.participant.session.datetime_ends > now: should_pay = False break if not should_pay: course_paid.append(course_id) exclude = {'id__in': course_paid} sessions = CourseSession.objects.filter( course__in=courses.exclude(**exclude), datetime_end_enroll__isnull=False, datetime_start_enroll__lt=now).exclude( **exclude).order_by('-datetime_end_enroll') courses_with_sessions = defaultdict(list) for s in sessions: courses_with_sessions[s.course_id].append(s) for c, course_sessions in courses_with_sessions.items(): if course_sessions: session_for_course[c] = course_sessions[0] types = dict([ (i.session.id, i.price) for i in SessionEnrollmentType.objects.filter( session__in=list(session_for_course.values()), mode='verified') ]) result = {'courses': []} for c in courses: s = session_for_course.get(c.id) if s: result['courses'].append((c, types.get(s.id, 0))) else: result['courses'].append((c, 0)) price = sum([i[1] for i in result['courses']]) whole_price = price * (1 - self.discount / 100.) result.update({ 'price': price, 'whole_price': whole_price, 'discount': self.discount }) return result def get_start_date(self): """ дата старта первого курса модуля """ c = self.courses.first() if c and c.next_session: return c.next_session.datetime_starts def course_status_params(self): from .utils import get_status_dict c = self.get_closest_course_with_session() if c: return get_status_dict(c[1]) return {} @property def count_courses(self): return self.courses.count() @cached_property def courses_with_closest_sessions(self): from .utils import choose_closest_session courses = self.courses.exclude(extended_params__is_project=True) return [(c, choose_closest_session(c)) for c in courses] def get_closest_course_with_session(self): """ первый курс, не являющийся проектом, и соответствующая сессия модуля """ for c in self.courses.filter(extended_params__is_project=False): session = c.next_session if session and session.get_verified_mode_enrollment_type(): return c, session def may_enroll(self): """ Проверка того, что пользователь может записаться на модуль :return: bool """ courses = self.courses_with_closest_sessions return all(i[1] and i[1].allow_enrollments() for i in courses) def may_enroll_on_project(self, user): """ Проверка того, что пользователь может записаться на проект :param user: User :return: bool """ if not user.is_authenticated: return False if not EducationalModuleEnrollment.objects.filter( user=user, module=self, is_active=True).exists(): return False courses = self.courses.filter( extended_params__is_project=False).values_list('id', flat=True) passed = {i: False for i in courses} participants = Participant.objects.filter( session__course__id__in=courses, user=user).values_list('session__course__id', 'is_graduate') for course_id, is_graduate in participants: if is_graduate: passed[course_id] = True return all(i for i in list(passed.values())) def get_available_enrollment_types(self, mode=None, exclude_expired=True, active=True): """ Возвращает доступные варианты EducationalModuleEnrollmentType для текущего модуля """ qs = EducationalModuleEnrollmentType.objects.filter(module=self) if active: qs = qs.filter(active=True) if mode: qs = qs.filter(mode=mode) if exclude_expired and mode == 'verified': qs = qs.exclude(buy_expiration__lt=timezone.now()).filter( models.Q(buy_start__isnull=True) | models.Q(buy_start__lt=timezone.now())) return qs def get_verified_mode_enrollment_type(self): """ Метод аналогичный CourseSession """ return self.get_available_enrollment_types(mode='verified').first() def get_enrollment_reason_for_user(self, user): """ queryset EducationalModuleEnrollmentReason для пользователя, первый элемент - полностью оплаченный, если такой есть """ if user.is_authenticated: return EducationalModuleEnrollmentReason.objects.filter( enrollment__user=user, enrollment__module=self, ).order_by('-full_paid').first() def get_first_session_to_buy(self, user): """ Сессия первого курса, который пользователь может купить. Возвращает (сессия, цена) или None """ auth = user.is_authenticated if user else None for course in self.courses.exclude(extended_params__is_project=True): session = course.next_session if session: enr_type = session.get_verified_mode_enrollment_type() if enr_type and auth: if not enr_type.is_user_enrolled(user): return session, enr_type.price elif enr_type: return session, enr_type.price
"""Aerial position model.""" import logging from auvsi_suas.models import distance from auvsi_suas.models.gps_position import GpsPosition from django.contrib import admin from django.core import validators from django.db import models logger = logging.getLogger(__name__) ALTITUDE_MSL_FT_MIN = -2000 # Lowest point on earth with buffer. ALTITUDE_MSL_FT_MAX = 396000 # Edge of atmosphere. ALTITUDE_VALIDATORS = [ validators.MinValueValidator(ALTITUDE_MSL_FT_MIN), validators.MaxValueValidator(ALTITUDE_MSL_FT_MAX), ] class AerialPosition(models.Model): """Aerial position which consists of a GPS position and an altitude.""" # GPS position. gps_position = models.ForeignKey(GpsPosition, on_delete=models.CASCADE) # Altitude (MSL) in feet. altitude_msl = models.FloatField(validators=ALTITUDE_VALIDATORS) def distance_to(self, other): """Computes distance to another position. Args:
def __init__(self, *args, **kwargs): kwargs['validators'] = (validators.MinValueValidator(0.0), validators.MaxValueValidator(1.0)) super().__init__(*args, **kwargs)
class Transaction(TimestampModel): uuid = models.UUIDField(default=uuid.uuid4, primary_key=True) type = models.CharField( _("Type"), max_length=254, default=constants.EXPENDITURE, choices=constants.TRANSACTION_TYPE_CHOICES, ) amount = models.DecimalField( _("Amount"), max_digits=12, decimal_places=2, validators=[validators.MinValueValidator(0)], ) author = models.ForeignKey(UserModel, related_name="transactions", on_delete=models.CASCADE) category = models.ForeignKey( Category, related_name="transactions", on_delete=models.SET_NULL, verbose_name=_("Category"), blank=True, null=True, ) tags = models.ManyToManyField(Tag, related_name="transactions", verbose_name=_("Tags")) account = models.ForeignKey( "accounts.Account", related_name="transactions", on_delete=models.CASCADE, verbose_name=_("Account"), ) expenditure_counterpart = models.OneToOneField( "self", related_name="income_counterpart", on_delete=models.CASCADE, verbose_name=_("Expenditure counterpart"), blank=True, null=True, ) description = models.CharField(_("Description"), max_length=1000, blank=True) objects = managers.TransactionManager() class Meta: ordering = ["-created_at"] verbose_name = _("Transaction") verbose_name_plural = _("Transactions") def __str__(self): return f"{self.get_type_display()} - {self.amount}" def get_absolute_url(self): return reverse("transactions:detail", kwargs={"pk": self.pk}) def save(self, *args, **kwargs): expenditure_condition = (self.expenditure_counterpart and self.type == constants.EXPENDITURE) try: income_condition = self.income_counterpart and self.type == constants.INCOME except Transaction.DoesNotExist: income_condition = False if expenditure_condition or income_condition: raise ValueError( _("Cannot change the type of a transaction that is part of a transfer" )) return super().save(*args, **kwargs) @property def is_income(self): return self.type == constants.INCOME @property def is_expenditure(self): return self.type == constants.EXPENDITURE @property def type_icon_class(self): if self.is_expenditure: return "text-danger fa fa-arrow-circle-down" else: return "text-success fa fa-arrow-circle-up" @property def currency(self): return self.account.currency @property def is_part_of_transfer(self): try: return self.expenditure_counterpart or self.income_counterpart except Transaction.DoesNotExist: return False @property def exchange_rate(self): if not self.is_part_of_transfer: return divisible = self.amount if self.is_income else self.income_counterpart.amount divisor = self.expenditure_counterpart.amount if self.is_income else self.amount return divisible / divisor
class Person(models.Model): first_name = models.CharField( max_length=70, verbose_name='Prénom', validators=name_validators, ) last_name = models.CharField( max_length=70, verbose_name='Nom de famille', validators=name_validators, ) birth_date = models.DateField( null=True, verbose_name='Date de naissance', validators=[ validators.MinValueValidator( datetime.date(year=1900, month=1, day=1)) ], ) email = models.EmailField( null=True, verbose_name='Email', ) use_email = models.BooleanField( default=False, null=True, blank=True, verbose_name="L'assuré autorise à utiliser son email.", ) nir = models.CharField( max_length=13, verbose_name='Numéro de sécurité sociale', validators=[ nir_validate_alphanumeric, validators.MinLengthValidator(13), # note that max_length attribute implies a max length validator ]) shifted = models.NullBooleanField( default=None, null=True, blank=True, verbose_name='Assuré a basculé', ) creation_datetime = models.DateTimeField( default=timezone.now, db_index=True, verbose_name='Date et heure de création', ) class Meta: ordering = ( 'last_name', 'first_name', ) verbose_name = 'Personne' def __str__(self): return '%s %s %s' % (self.first_name, self.last_name, self.birth_date) def save(self, *args, **kwargs): """ Validate all fields, since validators are not run on save by default. """ self.full_clean() return super(Person, self).save(*args, **kwargs) def get_dates(self): dates = {'depart': dict(), 'return': dict()} valids = self.mrsrequest_set.filter( status=self.mrsrequest_set.model.STATUS_VALIDATED ).prefetch_related('transport_set') for mrsrequest in valids: transports = mrsrequest.transport_set.exclude(date_depart=None) for transport in transports: for i in ('depart', 'return'): value = getattr(transport, f'date_{i}') if i == 'return' and not value: continue if value not in dates[i].keys(): dates[i][value] = [] dates[i][value].append(mrsrequest) return dates def get_duplicate_dates(self): dupes = {'depart': dict(), 'return': dict()} for i, dates in self.get_dates().items(): for date, mrsrequests in dates.items(): if len(mrsrequests) == 1: continue dupes[i][date] = mrsrequests return {k: v for k, v in dupes.items() if v}
class Item(models.Model): TYPE_CHOICES = ( (1, 'TVアニメ'), (2, '劇場アニメ'), ) MEDIA_CHOICES = ((1, 'BD'), (2, 'DVD'), (3, 'BD/DVD両方')) title = models.CharField( verbose_name='タイトル', max_length=250, ) place = models.CharField( verbose_name='保管場所', max_length=250, ) type = models.IntegerField( verbose_name='TV/映画', choices=TYPE_CHOICES, default=1, ) media = models.IntegerField( verbose_name='メディア形式', choices=MEDIA_CHOICES, default=1, ) year = models.IntegerField( verbose_name='制作年', validators=[validators.MinValueValidator(1963)], blank=True, null=True, ) epsode_number = models.IntegerField( verbose_name='話数', validators=[validators.MinValueValidator(1)], blank=True, null=True, ) media_number = models.IntegerField( verbose_name='巻数', validators=[validators.MinValueValidator(1)], blank=True, null=True, ) production = models.CharField( verbose_name='制作会社', max_length=250, blank=True, null=True, ) voiceactor = models.CharField( verbose_name='主演声優', max_length=250, blank=True, null=True, ) director = models.CharField( verbose_name='監督', max_length=200, blank=True, null=True, ) memo = models.TextField( verbose_name='備考', max_length=300, blank=True, null=True, ) created_at = models.DateTimeField( verbose_name='登録日', auto_now_add=True, blank=True, null=True, ) # 以下は管理サイト上の表示設定 def _str_(self): return self.name class Meta: verbose_name = 'アイテム' verbose_name_plural = 'アイテム'
class Cache(models.Model): defined_storage_name = models.CharField( db_index=True, help_text=_( 'Internal name of the defined storage for this cache.' ), max_length=96, unique=True, verbose_name=_('Defined storage name') ) maximum_size = models.BigIntegerField( help_text=_('Maximum size of the cache in bytes.'), validators=[ validators.MinValueValidator(limit_value=1) ], verbose_name=_('Maximum size') ) class Meta: verbose_name = _('Cache') verbose_name_plural = _('Caches') def __str__(self): return force_text(self.label) def get_files(self): return CachePartitionFile.objects.filter(partition__cache__id=self.pk) def get_maximum_size_display(self): return filesizeformat(bytes_=self.maximum_size) get_maximum_size_display.help_text = _( 'Size at which the cache will start deleting old entries.' ) get_maximum_size_display.short_description = _('Maximum size') def get_defined_storage(self): return DefinedStorage.get(name=self.defined_storage_name) def get_total_size(self): """ Return the actual usage of the cache. """ return self.get_files().aggregate( file_size__sum=Sum('file_size') )['file_size__sum'] or 0 def get_total_size_display(self): return format_lazy( '{} ({:0.1f}%)', filesizeformat(bytes_=self.get_total_size()), self.get_total_size() / self.maximum_size * 100 ) get_total_size_display.short_description = _('Current size') get_total_size_display.help_text = _('Current size of the cache.') @cached_property def label(self): return self.get_defined_storage().label def prune(self): """ Deletes files until the total size of the cache is below the allowed maximum size of the cache. """ while self.get_total_size() > self.maximum_size: self.get_files().earliest().delete() def purge(self, _user=None): """ Deletes the entire cache. """ for partition in self.partitions.all(): partition.purge() event_cache_purged.commit(actor=_user, target=self) def save(self, *args, **kwargs): _user = kwargs.pop('_user', None) with transaction.atomic(): is_new = not self.pk result = super(Cache, self).save(*args, **kwargs) if is_new: event_cache_created.commit( actor=_user, target=self ) else: event_cache_edited.commit( actor=_user, target=self ) self.prune() return result @cached_property def storage(self): return self.get_defined_storage().get_storage_instance()
class ActivityPerformance(Performance): # Uses basic Performance to bind a duration & description to a contract """Activity performance model.""" contract = models.ForeignKey(Contract, on_delete=models.PROTECT) performance_type = models.ForeignKey(PerformanceType, on_delete=models.PROTECT) contract_role = models.ForeignKey(ContractRole, null=True, on_delete=models.PROTECT) description = models.TextField(max_length=255, blank=True, null=True) duration = models.DecimalField(max_digits=4, decimal_places=2, default=1.00, validators=[ validators.MinValueValidator( Decimal('0.01')), validators.MaxValueValidator(24), ]) def __str__(self): """Return a string representation.""" return '%s - %s' % (self.performance_type, super().__str__()) @classmethod def perform_additional_validation(cls, data, instance=None): """Perform additional validation on the object.""" super().perform_additional_validation(data, instance=instance) instance_id = instance.id if instance else None # noqa contract = data.get('contract', getattr(instance, 'contract', None)) performance_type = data.get( 'performance_type', getattr(instance, 'performance_type', None)) contract_role = data.get('contract_role', getattr(instance, 'contract_role', None)) if contract and contract_role: # Ensure that contract is a project contract if not isinstance(contract, ProjectContract): raise ValidationError( _('The selected contract role is not valid for the selected contract.' ), ) # Ensure that contract role is valid for contract performance = data.get('performance', getattr(instance, 'performance', None)) if performance: timesheet = Timesheet.objects.get(id=performance.timesheet) user_id = timesheet.user if timesheet else None # noga contract_user = ContractUser.objects.filter( user=user_id, contract=contract.id, contract_role=contract_role) if not contract_user: raise ValidationError( _('The selected contract role is not valid for the current user.' ), ) if contract and performance_type: # Ensure the performance type is valid for the contract allowed_types = list(contract.performance_types.all()) if allowed_types and (performance_type not in allowed_types): raise ValidationError( _('The selected performance type is not valid for the selected contract' ), ) def get_validation_args(self): """Get a dict used for validation based on this instance.""" return merge_dicts( super().get_validation_args(), { 'contract': getattr(self, 'contract', None), 'performance_type': getattr(self, 'performance_type', None), })
class Ticket(models.Model): """ 販売されているチケット """ STATUS_DISPLAY = 0 STATUS_STOPPED = 1 STATUS_SOLD_OUT = 2 STATUS_CHOICES = ( (STATUS_DISPLAY, '出品中'), # 現在出品されている。購入可能な状態 (STATUS_STOPPED, '出品停止'), # 以降購入ができない状態。購入済みのチケットは有効 (STATUS_SOLD_OUT, '完売') # 出品したチケットが売切れた状態 ) seller = models.ForeignKey('tbpauth.User', on_delete=models.CASCADE, related_name='selling_tickets') name = models.CharField("チケット名", max_length=128) category = models.ForeignKey(Category, verbose_name="カテゴリー", on_delete=models.SET_NULL, null=True, blank=True, related_name='tickets') start_date = models.DateField("開催日") price = models.PositiveIntegerField("金額(円)", validators=[ validators.MinValueValidator(100), StepValueValidator(100) ]) quantity = models.PositiveIntegerField( "販売枚数(枚)", validators=[validators.MinValueValidator(1)]) status = models.PositiveIntegerField("販売ステータス", choices=STATUS_CHOICES, default=STATUS_DISPLAY) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = 'ticket' verbose_name = 'チケット' verbose_name_plural = 'チケット' def __str__(self): return self.name def get_absolute_url(self): return reverse('tickets:detail', kwargs={'ticket_id': self.id}) def status_is_display(self): """ ステータスが「出品中」の場合にTrueを返す """ return self.status == self.STATUS_DISPLAY def fee_rate(self): """ 手数料の割合を小数で返す 販売枚数とカテゴリーの追加手数料から計算して小数で返す """ if self.quantity < 50: fee_rate = 0.05 elif self.quantity < 100: fee_rate = 0.03 else: fee_rate = 0.01 if self.category: fee_rate += self.category.extra_fee_rate return fee_rate def fee(self): """ 手数料の金額を返す """ return int(round(self.fee_rate() * self.price)) def stock_amount(self): """ チケットの残り在庫数を返す 販売枚数から購入履歴 Purchase モデルの合計枚数をマイナスして計算する。 """ agg = self.purchases.aggregate(sum_amount=models.Sum('amount')) return self.quantity - (agg['sum_amount'] or 0) # 以下、表示用の関数。値に単位などを追加して文字列として返す def price_display(self): return '{:,d}円'.format(self.price) def quantity_display(self): return '{:,d}枚'.format(self.quantity) def fee_rate_display(self): return '{:0.0f}%'.format(self.fee_rate() * 100) def fee_display(self): return '{:,d}円 / 枚'.format(self.fee()) def stock_amount_display(self): return '{:,d}枚'.format(self.stock_amount())
class Application(models.Model): organization = models.ForeignKey(Organization, unique_for_year='year') year = afap.modelfields.YearField() president = models.ForeignKey(Person, related_name='president', null=True, blank=True) email_president = models.BooleanField(default=True) treasurer = models.ForeignKey(Person, related_name='treasurer', null=True, blank=True) email_treasurer = models.BooleanField(default=True) advisor = models.ForeignKey(Person, related_name='advisor', null=True, blank=True) email_advisor = models.BooleanField(default=True) members = models.IntegerField("number of members", default=0, validators=[validators.MinValueValidator(0)]) dues = models.DecimalField(default='0', max_digits=DECIMAL_DIGITS, decimal_places=DECIMAL_PLACES) new_members = models.IntegerField("anticipated new members", default=0) purpose = models.TextField(blank=True) membership_requirements = models.TextField(blank=True) note = models.TextField("notes", blank=True) last_modified = models.DateTimeField(blank=True) provisional = models.BooleanField(default=False) balance_forward = models.DecimalField("Balance forward", max_digits=DECIMAL_DIGITS, decimal_places=DECIMAL_PLACES, default=0) afap_income = models.DecimalField("AFAP Income", max_digits=DECIMAL_DIGITS, decimal_places=DECIMAL_PLACES, default=0, validators=[validators.MinValueValidator(0)]) def __unicode__(self): return u'Application for %s (%d)' % (self.organization.name, self.year.year,) def _year_for_admin(self): return self.year.year _year_for_admin.admin_order_field = 'year' _year_for_admin.short_description = 'year' def has_org_info(self): return (self.members > 0 or self.dues > 0 or self.new_members != 0 or self.purpose != '' or self.membership_requirements != '') def has_budget_request(self): return (self.lineitem_set.exists() or self.balance_forward != 0 or self.afap_income != 0) def approved(self): for approval in self.approval_set.all(): if not approval.approved: return False return True def request_amount(self): sums = [] for category in LineItemCategory.objects.filter(years__date=self.year): sums.append(sum_line_items( LineItem.objects.filter(application=self, category=category))) total_sum = sum(sums) - self.balance_forward - self.afap_income total_sum = total_sum or 0 return total_sum # look into only updating last_modified when the user makes a chance, # not when an admin does def save(self, *args, **kwargs): '''On save, update last_modified''' self.last_modified = datetime.datetime.now() approvers = (self.president, self.treasurer, self.advisor) for approval in self.approval_set.all(): if approval.approver not in approvers: approval.delete() else: approval.approved = False approval.approved_at = None approval.generate_key() approval.save() for person in [person for person in (self.president, self.treasurer, self.advisor) if person is not None]: try: self.approval_set.get(approver=person) except (Approval.DoesNotExist): Approval(application=self, approver=person).save() return super(Application, self).save(*args, **kwargs) def print_to_pdf(self, pdf): return def organization_constituency(self): return self.organization.constituency_group organization_constituency.description = "the applying organization's const" def email(self, subject, message, from_, tuple_=False): recps = [] if self.president and self.email_president: recps.append(self.president.detailed_address()) if self.treasurer and self.email_treasurer: recps.append(self.treasurer.detailed_address()) if self.advisor and self.email_advisor: recps.append(self.advisor.detailed_address()) if tuple_: return (subject, message, from_, recps) else: mail.send_mail(subject, message, from_, recps, fail_silently=False) return
CONSTANCE_SUPERUSER_ONLY = False CONSTANCE_ADDITIONAL_FIELDS = { 'TIMEZONE': ('django.forms.fields.ChoiceField', { 'widget': 'django.forms.Select', 'choices': [(name, name) for name in pytz.common_timezones], }), 'WAIT_INTERVAL_MINUTES': ('django.forms.fields.IntegerField', { 'widget': 'django.forms.TextInput', 'widget_kwargs': { 'attrs': { 'size': 10 } }, 'validators': [validators.MinValueValidator(0), validators.MaxValueValidator(600)], }), 'FADE_ASSETS_MS': ('django.forms.fields.IntegerField', { 'widget': 'django.forms.TextInput', 'widget_kwargs': { 'attrs': { 'size': 10 } }, 'validators': [validators.MinValueValidator(0), validators.MaxValueValidator(10000)], }), }
class MRSRequestCreateForm(forms.ModelForm): # do not trust this field, it's used for javascript and checked # by the view for permission against the request session, but is # NOT to be trusted as input: don't use data['mrsrequest_uuid'] nor # cleaned_data['mrsrequest_uuid'], you've been warned. mrsrequest_uuid = forms.CharField(widget=forms.HiddenInput, required=False) pmt = MRSAttachmentField( PMT, 'mrsrequest:pmt_upload', 'mrsrequest:pmt_download', 20, label='Prescription Médicale de Transport obligatoire', help_text=PMT_HELP, required=False, ) pmt_pel = forms.ChoiceField( choices=( ('pmt', 'Prescription papier (PMT)'), ('pel', 'Prescription électronique (PMET)'), ('convocation', 'Convocation Service Médical'), ), initial='pmt', label='', widget=forms.RadioSelect, ) pel = forms.CharField( label='Numéro de Prescription Électronique', help_text=PEL_HELP, required=False, ) convocation = DateFieldNative( label=( 'Date du rendez-vous avec le médecin' ' conseil de l\'Assurance Maladie' ), help_text='Au format jj/mm/aaaa, par exemple: 31/12/2000', required=False, ) billvps = MRSAttachmentField( BillVP, 'mrsrequest:billvp_upload', 'mrsrequest:bill_download', 20, label='Justificatifs', required=False, help_text=( 'Joindre vos justificatifs de péage' ' <span data-parking-enable>' ' / stationnement.' ' </span><br>' 'Format <b>jpeg</b>, <b>png</b> ou <b>pdf</b> -' '<b>4Mo maximum</b> par fichier.' ) ) billatps = MRSAttachmentField( BillATP, 'mrsrequest:billatp_upload', 'mrsrequest:bill_download', 20, label='Justificatifs', required=False, help_text=( 'Joindre vos justificatifs de transport en commun.<br>' 'Format <b>jpeg</b>, <b>png</b> ou <b>pdf</b> -' '<b>4Mo maximum</b> par fichier.' ) ) caisse = ActiveCaisseChoiceField( otherchoice=True, label='', help_text='Votre caisse n\'apparaît pas dans la liste ? Elle n\'a pas ' 'encore rejoint le dispositif MRS. Cliquez sur "Autre" pour ' 'la sélectionner et recevoir un e-mail dès que celle-ci ' 'sera disponible !' ) region = ActiveRegionChoiceField( label='', ) distancevp = CharFieldNative( label='Nombre total de kilomètres', help_text=' ', ) expenseatp = AllowedCommaDecimalField( decimal_places=2, max_digits=6, validators=[validators.MinValueValidator(Decimal('0.00'))], label='Frais de transports', help_text=( 'Somme totale des frais de transport en commun (en € TTC)' ), required=False, widget=forms.TextInput, ) expensevp_toll = AllowedCommaDecimalField( decimal_places=2, max_digits=6, validators=[validators.MinValueValidator(Decimal('0.00'))], label='Frais de péage', help_text=( 'Somme totale des frais de péage (en € TTC)' ), required=False, widget=forms.TextInput, ) expensevp_parking = AllowedCommaDecimalField( decimal_places=2, max_digits=6, validators=[validators.MinValueValidator(Decimal('0.00'))], label='Frais de stationnement', help_text=( 'Somme totale des frais de stationnement (en € TTC)' ), required=False, widget=forms.TextInput, ) layouts = dict( start=material.Layout( material.Fieldset( 'Votre région', 'region', ), ), above=material.Layout( material.Fieldset( 'Votre caisse d\'assurance maladie', 'caisse', ), ), top=material.Layout( material.Fieldset( 'Quel type de prescription médicale avez-vous reçue ' 'pour ce transport ?', 'pmt_pel', ), material.Row( 'pmt', ), material.Row( 'pel', ), material.Row( 'convocation', ), ), modevp=material.Layout( 'modevp', ), vp_form=material.Layout( 'distancevp', material.Row( 'expensevp_toll', 'expensevp_parking', ), 'billvps', ), modeatp=material.Layout( 'modeatp', ), atp_form=material.Layout( 'expenseatp', 'billatps', ), ) class Meta: model = MRSRequest fields = [ 'caisse', 'expenseatp', 'expensevp_parking', 'expensevp_toll', 'distancevp', 'modevp', 'modeatp', 'pel', 'convocation', ] widgets = dict( distancevp=forms.TextInput, expensevp_toll=forms.TextInput, expensevp_parking=forms.TextInput, ) def __init__(self, *args, **kwargs): kwargs.setdefault('initial', {}) initial = kwargs['initial'] kwargs['initial'].setdefault('expensevp_parking', 0) kwargs['initial'].setdefault('expensevp_toll', 0) if 'mrsrequest_uuid' in kwargs: mrsrequest_uuid = kwargs.pop('mrsrequest_uuid') instance = kwargs.get('instance') if not instance: kwargs['instance'] = MRSRequest() kwargs['instance'].id = mrsrequest_uuid elif 'instance' in kwargs: mrsrequest_uuid = str(kwargs['instance'].id) else: raise Exception('No instance, no uuid, secure it yourself') initial['mrsrequest_uuid'] = mrsrequest_uuid data, files, args, kwargs = self.args_extract(args, kwargs) if data: data, files = self.data_attachments(data, files, mrsrequest_uuid) kwargs['data'] = data kwargs['files'] = files super().__init__(*args, **kwargs) def cleaned_pmt_pel(self, cleaned_data): pmt_pel = cleaned_data.get('pmt_pel', 'pmt') if pmt_pel == 'pmt': cleaned_data.pop('pel', None) if not cleaned_data.get('pmt'): self.add_error('pmt', 'Merci de sélectionner votre PMT') elif pmt_pel == 'pel': if not cleaned_data.get('pel'): self.add_error('pel', 'Merci de saisir votre numéro de PMET') elif pmt_pel == 'convocation': if not cleaned_data.get('convocation'): self.add_error( 'convocation', 'Merci de saisir votre date de convocation' ) def cleaned_vp_atp(self, cleaned_data): vp = cleaned_data.get('modevp') atp = cleaned_data.get('modeatp') if not vp and not atp: self.add_error( 'modeatp', 'Merci de choisir véhicule personnel et / ou transports en' ' commun', ) self.add_error( 'modevp', 'Merci de choisir véhicule personnel et / ou transports en' ' commun', ) if vp: distancevp = cleaned_data.get('distancevp') if not distancevp: self.add_error( 'distancevp', 'Merci de saisir la distance du trajet', ) expensevp_toll = cleaned_data.get('expensevp_toll') expensevp_parking = cleaned_data.get('expensevp_parking') billvps = cleaned_data.get('billvps') if (expensevp_toll or expensevp_parking) and not billvps: self.add_error( 'billvps', 'Merci de soumettre vos justificatifs de transport' ) if atp: billatps = cleaned_data.get('billatps') if not billatps: self.add_error( 'billatps', 'Merci de fournir les justificatifs de transport', ) expenseatp = cleaned_data.get('expenseatp') if not expenseatp: self.add_error( 'expenseatp', 'Merci de saisir le total du coût de transports en commun', ) def clean(self): cleaned_data = super().clean() self.cleaned_pmt_pel(cleaned_data) self.cleaned_vp_atp(cleaned_data) return cleaned_data def data_attachments(self, data, files, mrsrequest_uuid): data['pmt'] = PMT.objects.recorded_uploads(mrsrequest_uuid) data['billvps'] = BillVP.objects.recorded_uploads(mrsrequest_uuid) data['billatps'] = BillATP.objects.recorded_uploads(mrsrequest_uuid) if files: files.update(data) else: files = data return data, files def args_extract(self, args, kwargs): """Extract data and files args, return mutable objects.""" # make popable (can't pop tuple of args) args = list(args) def getarg(name, num): if args and len(args) > num: return args.pop(num) elif kwargs.get('files'): return kwargs.pop('files') return None # First to not affect data = args.pop(0) files = getarg('files', 1) data = getarg('data', 0) # make mutable if something if files: files = MultiValueDict(files) if data: data = MultiValueDict(data) return data, files, args, kwargs def save(self, commit=True): if self.cleaned_data.get('parking_expensevp', None): self.instance.expensevp += self.cleaned_data.get( 'parking_expensevp') obj = super().save(commit=commit) def save_attachments(form, obj): if form.cleaned_data.get('pmt_pel') == 'pmt': obj.save_pmt() else: obj.delete_pmt() obj.save_bills() save_m2m = getattr(self, 'save_m2m', None) if save_m2m: def _save_m2m(): save_attachments(self, obj) save_m2m() self.save_m2m = _save_m2m else: save_attachments(self, obj) return obj
class StudyForm(models.Model): number = models.PositiveIntegerField(default = 1, validators = [ validators.MaxValueValidator(11), validators.MinValueValidator(1), ]) designator = models.TextField() objects = StudyFormManager() def __unicode__(self): return str(self.number) + self.designator def __str__(self): return str(self.number) + self.designator def get_url(self): return reverse('form', kwargs={'id': self.id,})
class User(AbstractUser): full_name = models.CharField(max_length=180) email = models.EmailField(blank=True) phone_number = models.CharField(max_length=16, blank=True) department = models.CharField( max_length=3, choices=DepartmentType.choices, default=DepartmentType.CSE.value, ) # Only for teachers designation = models.CharField( _('designation'), max_length=256, null=True, blank=True, ) qualification = models.CharField( _('qualification'), max_length=512, null=True, blank=True, ) profile_picture = models.ImageField( upload_to=generate_propic_upload_location, null=True, blank=True, ) cv_document = models.FileField( upload_to=generate_cv_upload_location, null=True, blank=True, ) is_teacher = models.BooleanField( _('teacher status'), help_text=_( 'Designates whether this user should be treated as teacher.' ), default=False, ) is_external = models.BooleanField( _('external teacher status'), help_text=_( 'Designates whether this teacher should be treated as an external teacher.' ), default=False, ) cgpa = models.DecimalField( null=True, blank=True, decimal_places=2, max_digits=5, validators=[ validators.MinValueValidator(2), validators.MaxValueValidator(4), ] ) is_student = models.BooleanField( _('student status'), help_text=_( 'Designates whether this user should be treated as student.'), default=False, ) is_superuser = models.BooleanField( _('Admin'), help_text=_( 'Designates whether this user should be treated as admin.'), default=False, ) studentgroup = models.ForeignKey( 'thesis.StudentGroup', related_name='students', on_delete=models.SET_NULL, null=True, blank=True, ) class Meta: ordering = ['username'] def studentgroup_count_by_batch(self, batch): return self.studentgroups.filter( approved=True, batch=batch, ).count() def __str__(self): name = self.username if self.full_name: name += f' - {self.full_name}' return name
class LessonTiming(models.Model): number = models.PositiveIntegerField(default = 1, validators = [ validators.MaxValueValidator(8), validators.MinValueValidator(1), ]) start = models.TimeField() end = models.TimeField()
class Peak(models.Model): location = models.PointField(null=False, blank=False) elevation = models.FloatField(null=False, blank=False, validators=[validators.MinValueValidator(0)]) name = models.CharField(max_length=128, null=False, blank=False)
class Alarm(models.Model): created = models.DateTimeField(auto_now_add=True) user = models.ForeignKey( User, related_name='alarms', on_delete=models.CASCADE ) origin = models.CharField( "Enderço de origem da carona", max_length=500 ) origin_point = models.PointField("Coordenadas de origem da carona") origin_radius = models.FloatField( "Raio de pesquisa da origem em km", default=5, validators=[validators.MaxValueValidator(10), validators.MinValueValidator(0.05)], ) destination = models.CharField( "Enderço de destino da carona", max_length=500 ) destination_point = models.PointField("Coordenadas de destino da carona") destination_radius = models.FloatField( "Raio de pesquisa do destino em km", default=5, validators=[validators.MaxValueValidator(20), validators.MinValueValidator(0.05)], ) price = models.PositiveSmallIntegerField("Preço máximo da carona em reais", null=True) auto_approve = models.NullBooleanField("Aprovação automática de passageiros", null=True) datetime_lte = models.DateTimeField("Datetime máxima de saída da carona", null=True) datetime_gte = models.DateTimeField("Datetime mínima de saída da carona", null=True) min_seats = models.PositiveSmallIntegerField( "Mínimo número de assentos na carona", validators=[validators.MaxValueValidator(10)], null=True ) @classmethod def find_and_send(cls, trip): """Find and send alarms Takes a newly created trip and searches through all alarms to find matches. If any are found, send them. """ # Start by filtering the easy ones # and then filter using expensive fields alarms = cls.objects.exclude( # Alarms that already ended should not be queried Q(datetime_lte__isnull=False) & Q(datetime_lte__lte=timezone.now()) ).filter( # If the alarm defined auto_approve, filter it Q(auto_approve__isnull=True) | Q(auto_approve=trip.auto_approve) ).filter( # If the alarm defined price, filter it Q(price__isnull=True) | Q(price__gte=trip.price) ).filter( # If the alarm defined min_seats, filter it Q(min_seats__isnull=True) | Q(min_seats__lte=trip.max_seats) ).filter( # If the alarm defined datetime_gte, filter it Q(datetime_gte__isnull=True) | Q(datetime_gte__lte=trip.datetime) ).filter( # If the alarm defined datetime_lte, filter it Q(datetime_lte__isnull=True) | Q(datetime_lte__gte=trip.datetime) ).annotate( # First annotate the distances, since django can't compute # F expressions inside D functions, per # https://gis.stackexchange.com/questions/176735/geodjango-distance-filter-based-on-database-column origin_distance=Distance('origin_point', trip.origin_point), destination_distance=Distance('destination_point', trip.destination_point) ).filter( # Filter origin origin_distance__lte=F('origin_radius') * 1000 ).filter( # Filter destination destination_distance__lte=F('destination_radius') * 1000 ) alarm_webhooks.MultipleAlarmsWebhook(alarms, trip).send() # Clear selected alarms alarms.delete()
class Pet(models.Model): """Main pet description""" """ - питомец: - кличка - возраст - пол (от до (интервалы)) - привит, не прививки - более подробный профиль питомца """ id = models.UUIDField(default=uuid.uuid4, primary_key=True) name = models.CharField(max_length=256, verbose_name="Кличка") age = models.IntegerField(verbose_name="Возраст", validators=[ validators.MinValueValidator(0), validators.MaxValueValidator(100) ]) doc = models.ForeignKey('petdocs.Registration', on_delete=models.CASCADE, verbose_name="Регистрационный документ", related_name="pet_registration") photo = models.ImageField(upload_to="img", blank=True, verbose_name=_("Фото")) species = models.ForeignKey('pets.Species', on_delete=models.CASCADE, verbose_name="Вид животного") breed = models.ForeignKey('pets.Breed', on_delete=models.CASCADE, verbose_name="Порода") owner = models.ForeignKey("petdocs.Owner", on_delete=models.CASCADE, verbose_name="Владелец", blank=True, null=True) status = models.ForeignKey("pets.PetStatus", on_delete=models.CASCADE, verbose_name="Статус питомца", blank=True, null=True) gender = models.ForeignKey('pets.Gender', on_delete=models.CASCADE, verbose_name="Пол") def save(self, force_insert=False, force_update=False, using=None, update_fields=None): self.doc.reg_num = self.breed.name[0] + self.doc.date.strftime( "%d%m%Y") self.doc.save() super().save() def make_word_end(self): word = "лет" n = self.age if n > 99: n = n % 100 if n in range(5, 21): word = "лет" elif n % 10 == 1: word = "год" elif n % 10 in range(2, 5): word = "года" return "{} {}".format(self.age, word) def __str__(self): return "{} ({}, {})".format(self.name, self.breed, self.make_word_end()) def get_absolute_url(self): return reverse('pet-detail', kwargs={'pk': self.pk})
class Event(models.Model, metaclass=ModelTranslateMeta): """Describes an event""" CATEGORY_ALUMNI = "alumni" CATEGORY_EDUCATION = "education" CATEGORY_CAREER = "career" CATEGORY_LEISURE = "leisure" CATEGORY_ASSOCIATION = "association" CATEGORY_OTHER = "other" EVENT_CATEGORIES = ( (CATEGORY_ALUMNI, _("Alumni")), (CATEGORY_EDUCATION, _("Education")), (CATEGORY_CAREER, _("Career")), (CATEGORY_LEISURE, _("Leisure")), (CATEGORY_ASSOCIATION, _("Association Affairs")), (CATEGORY_OTHER, _("Other")), ) DEFAULT_NO_REGISTRATION_MESSAGE = _("No registration required / " "Geen aanmelding vereist") title = MultilingualField(models.CharField, _("title"), max_length=100) description = MultilingualField( HTMLField, _("description"), help_text=_("Please fill in both of the description boxes (EN/NL)," " even if your event is Dutch only! Fill in the English " "description in Dutch then."), ) start = models.DateTimeField(_("start time")) end = models.DateTimeField(_("end time")) organiser = models.ForeignKey("activemembers.MemberGroup", models.PROTECT, verbose_name=_("organiser")) category = models.CharField( max_length=40, choices=EVENT_CATEGORIES, verbose_name=_("category"), help_text=_("Alumni: Events organised for alumni, " "Education: Education focused events, " "Career: Career focused events, " "Leisure: borrels, parties, game activities etc., " "Association Affairs: general meetings or " "any other board related events, " "Other: anything else."), ) registration_start = models.DateTimeField( _("registration start"), null=True, blank=True, help_text=_("If you set a registration period registration will be " "required. If you don't set one, registration won't be " "required. Prefer times when people don't have lectures, " "e.g. 12:30 instead of 13:37."), ) registration_end = models.DateTimeField( _("registration end"), null=True, blank=True, help_text=_("If you set a registration period registration will be " "required. If you don't set one, registration won't be " "required."), ) cancel_deadline = models.DateTimeField(_("cancel deadline"), null=True, blank=True) send_cancel_email = models.BooleanField( _("send cancellation notifications"), default=True, help_text=_("Send an email to the organising party when a member " "cancels their registration after the deadline."), ) location = MultilingualField( models.CharField, _("location"), max_length=255, ) map_location = models.CharField( _("location for minimap"), max_length=255, help_text=_("Location of Huygens: Heyendaalseweg 135, Nijmegen. " "Location of Mercator 1: Toernooiveld 212, Nijmegen. " "Not shown as text!!"), ) price = models.DecimalField( _("price"), max_digits=5, decimal_places=2, default=0, validators=[validators.MinValueValidator(0)], ) fine = models.DecimalField( _("fine"), max_digits=5, decimal_places=2, default=0, # Minimum fine is checked in this model's clean(), as it is only for # events that require registration. help_text=_("Fine if participant does not show up (at least €5)."), validators=[validators.MinValueValidator(0)], ) max_participants = models.PositiveSmallIntegerField( _("maximum number of participants"), blank=True, null=True, ) no_registration_message = MultilingualField( models.CharField, _("message when there is no registration"), max_length=200, blank=True, null=True, help_text=(format_lazy("{} {}", _("Default:"), DEFAULT_NO_REGISTRATION_MESSAGE)), ) published = models.BooleanField(_("published"), default=False) registration_reminder = models.ForeignKey( ScheduledMessage, on_delete=models.deletion.SET_NULL, related_name="registration_event", blank=True, null=True, ) start_reminder = models.ForeignKey( ScheduledMessage, on_delete=models.deletion.SET_NULL, related_name="start_event", blank=True, null=True, ) documents = models.ManyToManyField( "documents.Document", verbose_name=_("documents"), blank=True, ) slide = models.ForeignKey( Slide, verbose_name="slide", help_text=_("Change the header-image on the event's info-page to one " "specific to this event."), blank=True, on_delete=models.deletion.SET_NULL, null=True, ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._price = self.price self._registration_start = self.registration_start @property def after_cancel_deadline(self): return self.cancel_deadline and self.cancel_deadline <= timezone.now() @property def registration_started(self): return self.registration_start <= timezone.now() @property def registration_required(self): return bool(self.registration_start) or bool(self.registration_end) def has_fields(self): return self.registrationinformationfield_set.count() > 0 def reached_participants_limit(self): """Is this event up to capacity?""" return (self.max_participants is not None and self.max_participants <= self.eventregistration_set.filter(date_cancelled=None).count()) @property def registrations(self): """Queryset with all non-cancelled registrations""" return self.eventregistration_set.filter(date_cancelled=None) @property def participants(self): """Return the active participants""" if self.max_participants is not None: return self.registrations.order_by("date")[:self.max_participants] return self.registrations.order_by("date") @property def queue(self): """Return the waiting queue""" if self.max_participants is not None: return self.registrations.order_by("date")[self.max_participants:] return [] @property def cancellations(self): """Return a queryset with the cancelled events""" return self.eventregistration_set.exclude( date_cancelled=None).order_by("date_cancelled") @property def registration_allowed(self): now = timezone.now() return (bool(self.registration_start or self.registration_end) and self.registration_end > now >= self.registration_start) @property def cancellation_allowed(self): now = timezone.now() return (bool(self.registration_start or self.registration_end) and self.registration_start <= now < self.start) def is_pizza_event(self): try: self.pizzaevent return True except ObjectDoesNotExist: return False def clean(self): super().clean() errors = {} if self.start is None: errors.update( {"start": _("Start cannot have an empty date or time field")}) if self.end is None: errors.update( {"end": _("End cannot have an empty date or time field")}) if self.start is not None and self.end is not None: if self.end < self.start: errors.update( {"end": _("Can't have an event travel back in time")}) if self.registration_required: if self.fine < 5: errors.update({ "fine": _("The fine for this event is too low " "(must be at least €5).") }) for lang in settings.LANGUAGES: field = "no_registration_message_" + lang[0] if getattr(self, field): errors.update({ field: _("Doesn't make sense to have this " "if you require registrations.") }) if not self.registration_start: errors.update({ "registration_start": _("If registration is required, you need a start of " "registration") }) if not self.registration_end: errors.update({ "registration_end": _("If registration is required, you need an end of " "registration") }) if not self.cancel_deadline: errors.update({ "cancel_deadline": _("If registration is required, " "you need a deadline for the cancellation") }) elif self.cancel_deadline > self.start: errors.update({ "cancel_deadline": _("The cancel deadline should be" " before the start of the event.") }) if (self.registration_start and self.registration_end and (self.registration_start >= self.registration_end)): message = _("Registration start should be before " "registration end") errors.update({ "registration_start": message, "registration_end": message }) try: if (self.organiser is not None and self.send_cancel_email and self.organiser.contact_mailinglist is None): errors.update({ "send_cancel_email": _("This organiser does not " "have a contact mailinglist.") }) except ObjectDoesNotExist: pass if self.published: if (self.price != self._price and self._registration_start and self._registration_start <= timezone.now()): errors.update({ "price": _("You cannot change this field after " "the registration has started.") }) if (self._registration_start and self.registration_start != self._registration_start and self._registration_start <= timezone.now()): errors.update({ "registration_start": _("You cannot change this field after " "the registration has started.") }) if errors: raise ValidationError(errors) def get_absolute_url(self): return reverse("events:event", args=[str(self.pk)]) def save(self, *args, **kwargs): delete_collector = Collector( using=router.db_for_write(self.__class__, instance=self)) if not self.pk: super().save(*args, **kwargs) if self.published: if self.registration_required: registration_reminder_time = (self.registration_start - timezone.timedelta(hours=1)) registration_reminder = ScheduledMessage() if (self.registration_reminder is not None and not self.registration_reminder.sent): registration_reminder = self.registration_reminder if registration_reminder_time > timezone.now(): registration_reminder.title_en = "Event registration" registration_reminder.title_nl = "Evenement registratie" registration_reminder.body_en = ("Registration for '{}' " "starts in 1 hour".format( self.title_en)) registration_reminder.body_nl = ("Registratie voor '{}' " "start in 1 uur".format( self.title_nl)) registration_reminder.category = Category.objects.get( key=Category.EVENT) registration_reminder.time = registration_reminder_time registration_reminder.url = ( f"{settings.BASE_URL}" f'{reverse("events:event", args=[self.id])}') registration_reminder.save() self.registration_reminder = registration_reminder self.registration_reminder.users.set( Member.current_members.all()) elif registration_reminder.pk is not None: delete_collector.add([self.registration_reminder]) self.registration_reminder = None start_reminder_time = self.start - timezone.timedelta(hours=1) start_reminder = ScheduledMessage() if self.start_reminder is not None and not self.start_reminder.sent: start_reminder = self.start_reminder if start_reminder_time > timezone.now(): start_reminder.title_en = "Event" start_reminder.title_nl = "Evenement" start_reminder.body_en = f"'{self.title_en}' starts in " "1 hour" start_reminder.body_nl = f"'{self.title_nl}' begint over " "1 uur" start_reminder.category = Category.objects.get( key=Category.EVENT) start_reminder.time = start_reminder_time start_reminder.save() self.start_reminder = start_reminder if self.registration_required: self.start_reminder.users.set( [r.member for r in self.participants if r.member]) else: self.start_reminder.users.set(Member.current_members.all()) elif start_reminder.pk is not None: delete_collector.add([self.start_reminder]) self.start_reminder = None else: if (self.registration_reminder is not None and not self.registration_reminder.sent): delete_collector.add([self.registration_reminder]) self.registration_reminder = None if self.start_reminder is not None and not self.start_reminder.sent: delete_collector.add([self.start_reminder]) self.start_reminder = None super().save() delete_collector.delete() def delete(self, using=None, keep_parents=False): using = using or router.db_for_write(self.__class__, instance=self) collector = Collector(using=using) collector.collect([self], keep_parents=keep_parents) if (self.registration_reminder is not None and not self.registration_reminder.sent): collector.add([self.registration_reminder]) if self.start_reminder is not None and not self.start_reminder.sent: collector.add([self.start_reminder]) if self.is_pizza_event(): collector.add([self.pizzaevent]) return collector.delete() def __str__(self): return "{}: {}".format( self.title, timezone.localtime(self.start).strftime("%Y-%m-%d %H:%M")) class Meta: ordering = ("-start", ) permissions = (("override_organiser", "Can access events as if organizing"), )
class TeacherClassRegForm(FormWithRequiredCss): location_choices = [ (True, "I will use my own space for this class (e.g. space in my laboratory). I have explained this in 'Message for Directors' below."), (False, "I would like a classroom to be provided for my class.")] lateness_choices = [ (True, "Students may join this class up to 20 minutes after the official start time."), (False, "My class is not suited to late additions.")] hardness_choices = [ ("*", "* - Should be understandable to everyone in the class.",), ("**", "** - Should not be too difficult for most students.",), ("***", "*** - Will move quickly and will have many difficult parts.",), ("****", "**** - You should not expect to be able to understand most of this class.",), ] # The following is a dummy list (because using None causes an error). To enable class styles, admins should set the # Tag class_style_choices with value in the following JSON format, where the first element of each list # is the value stored in the database, and the second value is the option shown on the form. # [["Lecture", "Lecture Style Class"], ["Seminar", "Seminar Style Class"]] style_choices = [] # Grr, TypedChoiceField doesn't seem to exist yet title = StrippedCharField( label='Course Title', length=50, max_length=200 ) category = forms.ChoiceField( label='Course Category', choices=[], widget=BlankSelectWidget() ) class_info = StrippedCharField( label='Course Description', widget=forms.Textarea(), help_text='<span class="tex2jax_ignore">Want to enter math? Use <tt>$$ Your-LaTeX-code-here $$</tt>. (e.g. use $$\pi$$ to mention π)</span>' ) prereqs = forms.CharField( label='Course Prerequisites', widget=forms.Textarea(attrs={'rows': 4}), required=False, help_text='If your course does not have prerequisites, leave this box blank.') duration = forms.ChoiceField( label='Duration of a Class Meeting', help_text='(hours:minutes)', choices=[('0.0', 'Program default')], widget=BlankSelectWidget() ) num_sections = forms.ChoiceField( label='Number of Sections', choices=[(1,1)], widget=BlankSelectWidget(), help_text='(How many independent sections (copies) of your class would you like to teach?)' ) session_count = forms.ChoiceField( label='Number of Days of Class', choices=[(1,1)], widget=BlankSelectWidget(), help_text='(How many days will your class take to complete?)' ) # To enable grade ranges, admins should set the Tag grade_ranges. # e.g. [[7,9],[9,10],[9,12],[10,12],[11,12]] gives five grade ranges: 7-9, 9-10, 9-12, 10-12, and 11-12 grade_range = forms.ChoiceField( label='Grade Range', choices=[], required=False, widget=BlankSelectWidget() ) grade_min = forms.ChoiceField( label='Minimum Grade Level', choices=[(7, 7)], widget=BlankSelectWidget() ) grade_max = forms.ChoiceField( label='Maximum Grade Level', choices=[(12, 12)], widget=BlankSelectWidget() ) class_size_max = forms.ChoiceField( label='Maximum Number of Students', choices=[(0, 0)], widget=BlankSelectWidget(), validators=[validators.MinValueValidator(1)], help_text='The above class-size and grade-range values are absolute, not the "optimum" nor "recommended" amounts. We will not allow any more students than you specify, nor allow any students in grades outside the range that you specify. Please contact us later if you would like to make an exception for a specific student.' ) class_size_optimal = forms.IntegerField( label='Optimal Number of Students', help_text="This is the number of students you would have in your class in the most ideal situation. This number is not a hard limit, but we'll do what we can to try to honor this." ) optimal_class_size_range = forms.ChoiceField( label='Optimal Class Size Range', choices=[(0, 0)], widget=BlankSelectWidget() ) allowable_class_size_ranges = forms.MultipleChoiceField( label='Allowable Class Size Ranges', choices=[(0, 0)], widget=forms.CheckboxSelectMultiple(), help_text="Please select all class size ranges you are comfortable teaching." ) class_style = forms.ChoiceField( label='Class Style', choices=style_choices, required=False, widget=BlankSelectWidget()) hardness_rating = forms.ChoiceField( label='Difficulty',choices=hardness_choices, initial="**", help_text="Which best describes how hard your class will be for your students?") allow_lateness = forms.ChoiceField( label='Punctuality', choices=lateness_choices, widget=forms.RadioSelect() ) requested_room = forms.CharField( label='Room Request', required=False, help_text='If you have a specific room or type of room in mind, name a room at %s that would be ideal for you.' % settings.INSTITUTION_NAME ) requested_special_resources = forms.CharField( label='Special Requests', widget=forms.Textarea(), required=False, help_text="Write in any specific resources you need, like a piano, empty room, or kitchen. We cannot guarantee you any of the special resources you request, but we will contact you if we are unable to get you the resources you need. Please include any necessary explanations in the 'Message for Directors' box! " ) purchase_requests = forms.CharField( label='Planned Purchases', widget=forms.Textarea(), required=False, help_text='We give all teachers a $30 budget per class section for their classes; we can reimburse you if you turn in an itemized receipt with attached reimbursement form before the end of the program. If you would like to exceed this budget, please type a budget proposal here stating what you would like to buy, what it will cost, and why you would like to purchase it.' ) message_for_directors = forms.CharField( label='Message for Directors', widget=forms.Textarea(), required=False, help_text='Please explain any special circumstances and equipment requests. Remember that you can be reimbursed for up to $30 (or more with the directors\' approval) for class expenses if you submit itemized receipts.' ) def __init__(self, crmi, *args, **kwargs): from esp.program.controllers.classreg import get_custom_fields def hide_field(field, default=None): field.widget = forms.HiddenInput() if default is not None: field.initial = default def hide_choice_if_useless(field): """ Hide a choice field if there's only one choice """ if len(field.choices) == 1: hide_field(field, default=field.choices[0][0]) super(TeacherClassRegForm, self).__init__(*args, **kwargs) prog = crmi.program section_numbers = crmi.allowed_sections_actual section_numbers = zip(section_numbers, section_numbers) class_sizes = crmi.getClassSizes() class_sizes = zip(class_sizes, class_sizes) class_grades = crmi.getClassGrades() class_grades = zip(class_grades, class_grades) class_ranges = ClassSizeRange.get_ranges_for_program(prog) class_ranges = [(range.id, range.range_str()) for range in class_ranges] # num_sections: section_list; hide if useless self.fields['num_sections'].choices = section_numbers hide_choice_if_useless( self.fields['num_sections'] ) # category: program.class_categories.all() self.fields['category'].choices = [ (x.id, x.category) for x in prog.class_categories.all() ] # grade_min, grade_max: crmi.getClassGrades self.fields['grade_min'].choices = class_grades self.fields['grade_max'].choices = class_grades if Tag.getTag('grade_ranges'): grade_ranges = json.loads(Tag.getTag('grade_ranges')) self.fields['grade_range'].choices = [(range,str(range[0]) + " - " + str(range[1])) for range in grade_ranges] self.fields['grade_range'].required = True hide_field( self.fields['grade_min'] ) self.fields['grade_min'].required = False hide_field( self.fields['grade_max'] ) self.fields['grade_max'].required = False else: hide_field( self.fields['grade_range'] ) if crmi.use_class_size_max: # class_size_max: crmi.getClassSizes self.fields['class_size_max'].choices = class_sizes else: del self.fields['class_size_max'] if Tag.getBooleanTag('use_class_size_optimal', default=False): if not crmi.use_class_size_optimal: del self.fields['class_size_optimal'] if crmi.use_optimal_class_size_range: self.fields['optimal_class_size_range'].choices = class_ranges else: del self.fields['optimal_class_size_range'] if crmi.use_allowable_class_size_ranges: self.fields['allowable_class_size_ranges'].choices = class_ranges else: del self.fields['allowable_class_size_ranges'] else: del self.fields['class_size_optimal'] del self.fields['optimal_class_size_range'] del self.fields['allowable_class_size_ranges'] # decide whether to display certain fields # prereqs if not crmi.set_prereqs: self.fields['prereqs'].widget = forms.HiddenInput() # allow_lateness if not crmi.allow_lateness: self.fields['allow_lateness'].widget = forms.HiddenInput() self.fields['allow_lateness'].initial = 'False' self.fields['duration'].choices = sorted(crmi.getDurations()) hide_choice_if_useless( self.fields['duration'] ) # session_count if crmi.session_counts: session_count_choices = crmi.session_counts_ints session_count_choices = zip(session_count_choices, session_count_choices) self.fields['session_count'].choices = session_count_choices hide_choice_if_useless( self.fields['session_count'] ) # requested_room if not crmi.ask_for_room: hide_field( self.fields['requested_room'] ) # Hide resource fields since separate forms are now being used. - Michael P # Most have now been removed, but this one gets un-hidden by open classes. self.fields['requested_special_resources'].widget = forms.HiddenInput() # Add program-custom form components (for inlining additional questions without # introducing a separate program module) custom_fields = get_custom_fields() for field_name in custom_fields: self.fields[field_name] = custom_fields[field_name] # Modify help text on these fields if necessary. # TODO(benkraft): Is there a reason not to allow this on all fields? custom_helptext_fields = [ 'duration', 'class_size_max', 'num_sections', 'requested_room', 'message_for_directors', 'purchase_requests', 'class_info', 'grade_max', 'grade_min'] + custom_fields.keys() for field in custom_helptext_fields: tag_data = Tag.getProgramTag('teacherreg_label_%s' % field, prog) if tag_data: self.fields[field].label = tag_data tag_data = Tag.getProgramTag('teacherreg_help_text_%s' % field, prog) if tag_data: self.fields[field].help_text = tag_data # Hide fields as desired. tag_data = Tag.getProgramTag('teacherreg_hide_fields', prog) if tag_data: for field_name in tag_data.split(','): hide_field(self.fields[field_name]) tag_data = Tag.getProgramTag('teacherreg_default_min_grade', prog) if tag_data: self.fields['grade_min'].initial = tag_data tag_data = Tag.getProgramTag('teacherreg_default_max_grade', prog) if tag_data: self.fields['grade_max'].initial = tag_data tag_data = Tag.getProgramTag('teacherreg_default_class_size_max', prog) if tag_data: self.fields['class_size_max'].initial = tag_data # Rewrite difficulty label/choices if desired: if Tag.getTag('teacherreg_difficulty_choices'): self.fields['hardness_rating'].choices = json.loads(Tag.getTag('teacherreg_difficulty_choices')) # Get class_style_choices from tag, otherwise hide the field if Tag.getTag('class_style_choices'): self.fields['class_style'].choices = json.loads(Tag.getTag('class_style_choices')) self.fields['class_style'].required = True else: hide_field(self.fields['class_style']) # plus subprogram section wizard def clean(self): cleaned_data = self.cleaned_data # Make sure grade_min <= grade_max # We need to cast here until we can make the ChoiceFields into TypedChoiceFields. grade_min = cleaned_data.get('grade_min') grade_max = cleaned_data.get('grade_max') if grade_min and grade_max: grade_min = int(grade_min) grade_max = int(grade_max) if grade_min > grade_max: msg = u'Minimum grade must be less than the maximum grade.' self.add_error('grade_min', msg) self.add_error('grade_max', msg) # Make sure the optimal class size <= maximum class size. class_size_optimal = cleaned_data.get('class_size_optimal') class_size_max = cleaned_data.get('class_size_max') if class_size_optimal and class_size_max: class_size_optimal = int(class_size_optimal) class_size_max = int(class_size_max) if class_size_optimal > class_size_max: msg = u'Optimal class size must be less than or equal to the maximum class size.' self.add_error('class_size_optimal', msg) self.add_error('class_size_max', msg) if class_size_optimal == '': cleaned_data['class_size_optimal'] = None # If using grade ranges instead of min and max, extract min and max from grade range. if cleaned_data.get('grade_range'): cleaned_data['grade_min'], cleaned_data['grade_max'] = json.loads(cleaned_data.get('grade_range')) # Return cleaned data return cleaned_data def _get_total_time_requested(self): """ Get total time requested. Do not call before validation. """ return float(self.cleaned_data['duration']) * int(self.cleaned_data['num_sections'])
class Squirrel(models.Model): Latitude=models.DecimalField( max_digits=16, decimal_places=13, validators=[ validators.MaxValueValidator(41), validators.MinValueValidator(40), ], help_text=_('Latitude coordinate for squirrel sighting point'), ) Longitude=models.DecimalField( max_digits=16, decimal_places=13, validators=[ validators.MaxValueValidator(-73), validators.MinValueValidator(-74), ], help_text=_('Longitude coordinate for squirrel sighting point'), ) Unique_Squirrel_ID=models.CharField( unique=True, max_length=20, validators=[validators.RegexValidator("\d{1,2}[A-I]-[PA]M-\d{4}-\d{2}",message='Please enter the right ID.')], help_text=_('Identification tag for each squirrel sightings. The tag is comprised of "Hectare ID" + "Shift" + "Date" + "Hectare Squirrel Number.'), ) PM='PM' AM='AM' shift_choices=( (PM,'P.M.'), (AM,'A.M.'), ) Shift=models.CharField( max_length=2, choices=shift_choices, help_text=_('Value is either "AM" or "PM," to communicate whether or not the sighting session occurred in the morning or late afternoon.'), ) Date=models.DateField( help_text=_('Concatenation of the sighting session day,month and year.(formate: YYYY-MM-DD)'), ) ADULT='Adult' JUVENILE='Juvenile' age_choices=( (ADULT,'adult'), (JUVENILE,'juvenile'), ) Age=models.CharField( max_length=10, choices=age_choices, help_text=_('Value is either "adult" or "juvenile".'), blank=True, ) GRAY='Gray' CINNAMON='Cinnamon' BLACK='Black' fur_choices=( (GRAY,'gray'), (CINNAMON,'cinnamon'), (BLACK,'black'), ) Primary_Fur_Color=models.CharField( max_length=10, choices=fur_choices, help_text=_('Value is either "gray," "cinnamon" or "black".'), blank=True, ) GROUND_PLANE='Ground Plane' ABOVE_GROUND='Above Ground' location_choices=( (GROUND_PLANE,'ground plane'), (ABOVE_GROUND,'above ground'), ) Location=models.CharField( max_length=20, choices=location_choices, help_text=_('Value is either "ground plane" or "above ground." Sighters were instructed to indicate the location of where the squirrel was when first sighted.'), blank=True, ) Specific_Location=models.CharField( max_length=100, help_text=_('Sighters occasionally added commentary on the squirrel location. These notes are provided here.'), blank=True, ) Running=models.BooleanField( help_text=_('Squirrel was seen running.'), ) Chasing=models.BooleanField( help_text=_('Squirrel was seen chasing another squirrel.'), ) Climbing=models.BooleanField( help_text=_('Squirrel was seen climbing a tree or other environmental landmark.'), ) Eating=models.BooleanField( help_text=_('Squirrel was seen eating.'), ) Foraging=models.BooleanField( help_text=_('Squirrel was seen foraging for food.'), ) Other_Activities=models.CharField( max_length=100, blank=True, ) Kuks=models.BooleanField( help_text=_('Squirrel was heard kukking, a chirpy vocal communication used for a variety of reasons.'), ) Quaas=models.BooleanField( help_text=_('Squirrel was heard quaaing, an elongated vocal communication which can indicate the presence of a ground predator such as a dog.'), ) Moans=models.BooleanField( help_text=_('Squirrel was heard moaning, a high-pitched vocal communication which can indicate the presence of an air predator such as a hawk.'), ) Tail_flags=models.BooleanField( help_text=_("Squirrel was seen flagging its tail. Flagging is a whipping motion used to exaggerate squirrel's size and confuse rivals or predators. Looks as if the squirrel is scribbling with tail into the air.") ) Tail_twitches=models.BooleanField( help_text=_('Squirrel was seen twitching its tail. Looks like a wave running through the tail, like a breakdancer doing the arm wave. Often used to communicate interest, curiosity.'), ) Approaches=models.BooleanField( help_text=_('Squirrel was seen approaching human, seeking food.'), ) Indifferent=models.BooleanField( help_text=_('Squirrel was indifferent to human presence.'), ) Runs_from=models.BooleanField( help_text=_('Squirrel was seen running from humans, seeing them as a threat.'), ) def __str__(self): return self.Unique_Squirrel_ID
class Light(models.Model): title = models.CharField(null=False, blank=False, max_length=32) topic = models.CharField(null=False, blank=False, max_length=64, default="test_bulb/light/test_bulb/command") hcolor = ColorField(null=False, blank=False, help_text="Select color that fitd your mood.") brightness = models.FloatField(null=True, blank=True, validators=[ validators.MaxValueValidator(255), validators.MinValueValidator(0) ], default=100) color_temperature = models.IntegerField( null=True, blank=True, validators=[ validators.MaxValueValidator(6500), validators.MinValueValidator(2700) ], default=2700) color_from_temperature = models.BooleanField(null=False, blank=False) effect = models.CharField(null=True, blank=True, choices=[("rainbow", "Rainbow"), ("blink", "Blink")], max_length=32) graphql_fields = [ GraphQLString("title"), GraphQLString("topic"), GraphQLString("hcolor"), GraphQLString("brightness"), GraphQLString("color_temperature"), GraphQLString("color_from_temperature"), GraphQLString("effect"), ] search_fields = [ index.SearchField("title"), index.SearchField("topic"), index.SearchField("hcolor"), index.SearchField("brightness"), index.SearchField("color_temperature"), index.SearchField("color_from_temperature"), index.SearchField("effect"), ] panels = [ FieldPanel("title"), FieldPanel("topic"), FieldPanel("hcolor"), FieldPanel("brightness"), FieldPanel("color_temperature"), FieldPanel("color_from_temperature"), FieldPanel("effect"), ] def __str__(self): return f"{self.title}" def save(self, *args, **kwargs): client = mqtt.Client() hexc = self.hcolor.lstrip('#') rgbc = tuple(int(hexc[i:i + 2], 16) for i in (0, 2, 4)) client.connect("10.1.0.1", 1883, 60) client.publish(self.topic, '{"state":"ON",' + f'"brightness":{self.brightness},' + '"color":{' + f'"r":{rgbc[0]},"g":{rgbc[1]},"b":{rgbc[2]}' + '}}', qos=1) # "white_value":255,"color_temp":370 super(Light, self).save(*args, **kwargs)
class Stay(models.Model): ''' This is the model field that stores information about a stay. By default, new stays are not approved. The only optional field is the additional questions or concerns field. Each stay entry is linked to one Address entry. When creating a Stay object manually, call the full_clean() method before the save() method is called and catch any ValidationErrors to ensure the Stay is created with valid data. ''' class Meta: ordering = ['is_approved', 'in_date', 'guest'] verbose_name_plural = 'Stays' guest = models.ForeignKey(Guest, on_delete=models.PROTECT) in_date = models.DateField('check-in date', unique=True) out_date = models.DateField('check-out date', unique=True) is_using_custom_price = models.BooleanField(default=False) total_price = models.FloatField(default=0, validators=[ validators.MinValueValidator(0), ]) number_of_guests = models.PositiveIntegerField( default=1, validators=[ validators.MinValueValidator(1), validators.MaxValueValidator(6) ]) is_approved = models.BooleanField(default=False) is_fully_paid = models.BooleanField(default=False) additional_questions_or_concerns = models.TextField(default='', blank=True) def __str__(self): return f'{self.guest.name}: {"APPROVED" if self.is_approved else "PENDING"}' def clean(self): ''' This is the partial overriden version of the models.Model's clean method. It performs additional validation to ensure data is sufficient. Then, once the data is validated, calculates the total cost of the stay depending on the seasons. ''' super().clean() self._perform_additional_validation() if not self.is_using_custom_price: self._set_calculated_total_price() def _perform_additional_validation(self): self._ensure_checkout_occurs_before_checkin() self._ensure_global_setting_is_set() self._ensure_at_least_minimum_days_of_stay() self._ensure_no_conflicts_with_existing_stays() self._ensure_valid_number_of_guests() def _ensure_checkout_occurs_before_checkin(self): if self.in_date >= self.out_date: raise ValidationError( ('The check-in date must be before the check-out date.')) def _ensure_global_setting_is_set(self): if Globals.objects.all().count() != 1: raise ValidationError(('Administrative Error: Globals not set.')) def _ensure_at_least_minimum_days_of_stay(self): num_days = (self.out_date - self.in_date).days global_setting = Globals.objects.get(pk=1) if num_days < global_setting.minimum_days_of_stay: raise ValidationError(( f'A minimum of {global_setting.minimum_days_of_stay} day(s) required.' )) def _ensure_no_conflicts_with_existing_stays(self): num_days = (self.out_date - self.in_date).days taken_dates = data.get_taken_dates() for x in range(0, num_days + 1): current_date = self.in_date + datetime.timedelta(days=x) current_date = datetime.datetime.combine( current_date, datetime.datetime.min.time()).astimezone(pytz.utc) print(current_date) for taken_date in taken_dates: print(f'Checking against {taken_date}') if current_date == taken_date: raise ValidationError(( 'One or more dates of stay conflict with a current approved visit.' )) def _ensure_valid_number_of_guests(self): if self.number_of_guests < 1: raise ValidationError(('There must be at least one guest.')) def _set_calculated_total_price(self): num_days = (self.out_date - self.in_date).days self.total_price = 0 for x in range(0, num_days): current_date = self.in_date + datetime.timedelta(days=x) price_for_day = self._get_rate(current_date) self.total_price += price_for_day global_obj = Globals.objects.get(pk=1) self.total_price += global_obj.cleaning_fee self.total_price *= (((global_obj.state_tax_rate_percent + global_obj.county_tax_rate_percent) / 100.0) + 1) def _get_rate(self, date): price = Globals.objects.get(pk=1).default_price_per_night seasons = SeasonPricing.objects.all() for season in seasons: if date > season.start_date and date < season.end_date: price = season.price_per_night return price