class PreserveModelMixin(ModifiedModelMixin): """Base model to handle core objects. Defines created, modified, and deleted fields. Prevents deletion of this model and flags for exclusion from results. """ deleted_at = models.DateTimeField( auto_now=False, auto_now_add=False, blank=True, null=True, default=None, ) objects = QueryManager(deleted_at__isnull=True) all_objects = models.Manager() deleted_objects = QueryManager(deleted_at__isnull=False) class Meta: abstract = True @property def is_deleted(self): return bool(self.deleted_at) def delete(self, *args, **kwargs): pre_delete.send(sender=self.__class__, instance=self) self.deleted_at = now() self.__class__.objects.filter(id=self.id).update( deleted_at=self.deleted_at, ) post_delete.send(sender=self.__class__, instance=self)
class Branch(Publishable, Timestampable, models.Model): name = models.CharField(max_length=200) slug = models.SlugField(max_length=200) address_1 = models.CharField(max_length=100) address_2 = models.CharField(max_length=100, blank=True, null=True) address_3 = models.CharField(max_length=100, blank=True, null=True) town_city = models.CharField(max_length=50) county = models.CharField(max_length=50) postcode = models.CharField(max_length=10) location = models.PointField() telephone = models.CharField(max_length=20) email = models.EmailField() details = models.TextField() opening_hours = models.TextField() objects = models.Manager() active = QueryManager(status=Publishable.STATUS_CHOICE_ACTIVE) inactive = QueryManager(status=Publishable.STATUS_CHOICE_INACTIVE) def __str__(self): return self.name class Meta: ordering = ['name'] verbose_name = _('branch') verbose_name_plural = _('branches')
class PropertyType(Publishable, models.Model): name = models.CharField(max_length=50) slug = models.SlugField(max_length=50) objects = models.Manager() active = QueryManager(status=Publishable.STATUS_CHOICE_ACTIVE) inactive = QueryManager(status=Publishable.STATUS_CHOICE_INACTIVE) def __str__(self): return self.name class Meta: pass
class Picture(Publishable, Timestampable, Orderable, models.Model): caption = models.CharField(max_length=200) attachment = models.ImageField(upload_to='pictures/') objects = models.Manager() active = QueryManager(status=Publishable.STATUS_CHOICE_ACTIVE) inactive = QueryManager(status=Publishable.STATUS_CHOICE_INACTIVE) def __str__(self): return self.caption class Meta: abstract = True
class Media(Publishable, models.Model): media_type = models.ForeignKey(MediaType) description = models.CharField(max_length=200) attachment = models.FileField(upload_to='media/') objects = models.Manager() active = QueryManager(status=Publishable.STATUS_CHOICE_ACTIVE) inactive = QueryManager(status=Publishable.STATUS_CHOICE_INACTIVE) def __str__(self): return self.description class Meta: abstract = True
class Post(models.Model): published = models.BooleanField(default=False) confirmed = models.BooleanField(default=False) order = models.IntegerField() objects = models.Manager() public = QueryManager(published=True) public_confirmed = QueryManager( models.Q(published=True) & models.Q(confirmed=True)) public_reversed = QueryManager(published=True).order_by("-order") class Meta: ordering = ("order", )
class Post(models.Model): published = models.BooleanField() confirmed = models.BooleanField() order = models.IntegerField() objects = models.Manager() public = QueryManager(published=True) public_confirmed = QueryManager( models.Q(published=True) & models.Q(confirmed=True)) public_reversed = QueryManager(published=True).order_by('-order') class Meta: ordering = ('order', )
class Excerpt(Authorable, TimeStampedModel, StatusModel): STATUS = Choices("approved", "flagged", "rejected") content = models.TextField( max_length=1200, help_text="Brief excerpt describing this protein") proteins = models.ManyToManyField("Protein", blank=True, related_name="excerpts") reference = models.ForeignKey( Reference, related_name="excerpts", null=True, on_delete=models.SET_NULL, help_text="Source of this excerpt", ) objects = models.Manager() visible = QueryManager(~Q(status="rejected")) class Meta: ordering = ["reference__year", "created"] def __str__(self): ref = self.reference.citation if self.reference else "" return "{}: {}...".format(ref, self.content[:30]) def get_absolute_url(self): return self.reference.get_absolute_url()
class ApiKey(TimeStampedModel, SoftDeletableModel): api_key = models.CharField(max_length=50, null=False, blank=True, unique=True) live = ApiKeyManager() objects = SoftDeletableManager() removed = QueryManager(is_removed=True) @ecached_property('is_expire:{self.id}', 60) def is_expire(self): try: info = blockcypher.get_token_info(self.api_key) limits = info.get('limits', None) hits_history = info.get('hits_history', None) if not limits: return True if not hits_history: return False else: current_api_hour = sum( [i['api/hour'] for i in hits_history if 'api/hour' in i]) current_hooks_hour = sum([ i['hooks/hour'] for i in hits_history if 'hooks/hour' in i ]) if current_api_hour < limits['api/hour'] and \ current_hooks_hour < limits['hooks/hour']: return False except: return True def __str__(self): return self.api_key
class Transaction(TimeStampedModel, SoftDeletableModel): reference = models.CharField(max_length=255) wallet = models.ForeignKey('wallet.Wallet', related_name='transactions', on_delete=models.PROTECT) currency = models.CharField( max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH, default=settings.DEFAULT_CURRENCY, ) transaction_amount = models.DecimalField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, ) amount = MoneyField(amount_field="transaction_amount", currency_field="currency") is_pending = models.BooleanField(default=False) confirmed_at = models.DateTimeField(null=True, blank=True) objects = SoftDeletableManager() removed = QueryManager(is_removed=True) class Meta: ordering = ['wallet'] def __str__(self): return '({})'.format(self.wallet)
class WorkflowModel(models.Model): """Mixin for models that have a workflow state.""" _workflow_required = {} class WorkflowStates(models.TextChoices): PRIVATE = 'private', 'Private' PUBLISHED = 'published', 'Published' RETIRED = 'retired', 'Retired' STATUS = WorkflowStates.choices status = StatusField( db_index=True, help_text= 'Hidden from students when private (visible, but not listed, when retired).', ) status_changed = MonitorField( monitor='status', editable=False, ) class Meta: abstract = True objects = QueryManager() private = QueryManager(status='private') published = QueryManager(status='published') retired = QueryManager(status='retired') public = QueryManager(status__in=['published', 'retired']) def clean(self): """Require that all state requirements are met.""" super().clean() # noinspection PyUnresolvedReferences required = self._workflow_required.get(self.status) if not required: return True missing = { name: "Required to publish." for name in required if not getattr(self, name) } if missing: raise ValidationError(missing)
class Incident(StatusModel, TimeFramedModel, TimeStampedModel): title = models.CharField(max_length=200) description = models.CharField(max_length=500) STATUS = Choices('maintenance', 'partial_outage', 'major_outage') opened_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) system = models.ForeignKey(System, on_delete=models.CASCADE) objects = models.Manager() active = QueryManager( (models.Q(start__lte=now) | models.Q(start__isnull=True)) & (models.Q(end__gte=now) | models.Q(end__isnull=True))) closed = QueryManager(models.Q(end__lte=now)) def __str__(self): return f"{self.system} - {self.status} [{self.start}:{self.end}]"
class Item(TimeStampedModel): STATUS = Choices(u'yes', u'no', u'friends_only') name = models.CharField(max_length=255) slug = models.SlugField(max_length=255) description = models.TextField() status = StatusField() owner = models.ForeignKey(User, related_name=u'items') objects = models.Manager() public = QueryManager(status=u'yes').order_by(u'-modified') private = QueryManager(status=u'no').order_by(u'-modified') friends = QueryManager(status=u'friends_only').order_by(u'-modified') def __unicode__(self): return u'{0}'.format(self.name) def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super(Item, self).save(*args, **kwargs)
class Membership(TimeStampedModel, StatusModel): STATUS = Choices( ('active', 'Active'), ('suspended', 'Suspended') ) TITLES = Choices( ('storyteller', 'Storyteller'), ('coordinator', 'Coordinator') ) user = ForeignKey(settings.AUTH_USER_MODEL, CASCADE, related_name="memberships") organization = ForeignKey(Organization, CASCADE, related_name="memberships") title = CharField(max_length=20, choices=TITLES, blank=True) external_id = CharField(max_length=20, blank=True, verbose_name='External ID') prestige_total = PositiveIntegerField(default=0, editable=False) prestige_level = ForeignKey('PrestigeLevel', SET_NULL, null=True, editable=False, related_name='memberships') objects = Manager() storytellers = QueryManager(title='storyteller') coordinators = QueryManager(title='coordinator') class Meta: unique_together = ('user', 'organization',) ordering = ('organization__name', 'user__username') def __str__(self): return f'#{self.external_id or self.pk} {self.user}' def recalculate_prestige(self): total = self.prestige.filter(amount__gt=0).all().aggregate(Sum('amount'))['amount__sum'] self.prestige_total = total try: self.prestige_level = PrestigeLevel.objects.filter( organization=self.organization, prestige_required__lte=total).order_by('-prestige_required').first() except PrestigeLevel.DoesNotExist: logger.error(f'Missing prestige level for total >= {total}') self.save()
def add_status_query_managers(sender, **kwargs): """ Add a Querymanager for each status item dynamically. """ if not issubclass(sender, StatusModel): return for value, display in getattr(sender, 'STATUS', ()): if _field_exists(sender, value): raise ImproperlyConfigured( "StatusModel: Model '%s' has a field named '%s' which " "conflicts with a status of the same name." % (sender.__name__, value)) sender.add_to_class(value, QueryManager(status=value))
class AgendaHora(models.Model): agenda = models.ForeignKey(Agenda, related_name='horarios', on_delete=models.PROTECT) hora = models.TimeField() disponivel = models.BooleanField('disponível', default=True, editable=False) objects = models.Manager() disponiveis = QueryManager(disponivel=True) class Meta: verbose_name = 'Horário' ordering = ['hora'] unique_together = ['agenda', 'hora'] def __str__(self): return self.hora.strftime('%H:%M')
def add_timeframed_query_manager(sender, **kwargs): """ Add a QueryManager for a specific timeframe. """ if not issubclass(sender, TimeFramedModel): return if _field_exists(sender, 'timeframed'): raise ImproperlyConfigured( "Model '%s' has a field named 'timeframed' " "which conflicts with the TimeFramedModel manager." % sender.__name__) sender.add_to_class( 'timeframed', QueryManager((models.Q(start__lte=now) | models.Q(start__isnull=True)) & (models.Q(end__gte=now) | models.Q(end__isnull=True))))
def add_status_query_managers(sender, **kwargs): """ Add a Querymanager for each status item dynamically. """ if not issubclass(sender, StatusModel): return for value, display in getattr(sender, 'STATUS', ()): try: sender._meta.get_field(value) raise ImproperlyConfigured("StatusModel: Model '%s' has a field " "named '%s' which conflicts with a " "status of the same name." % (sender.__name__, value)) except FieldDoesNotExist: pass sender.add_to_class(value, QueryManager(status=value))
def add_timeframed_query_manager(sender, **kwargs): """ Add a QueryManager for a specific timeframe. """ if not issubclass(sender, TimeFramedModel): return try: sender._meta.get_field('timeframed') raise ImproperlyConfigured("Model '%s' has a field named " "'timeframed' which conflicts with " "the TimeFramedModel manager." % sender.__name__) except FieldDoesNotExist: pass sender.add_to_class('timeframed', QueryManager( (models.Q(start__lte=now) | models.Q(start__isnull=True)) & (models.Q(end__gte=now) | models.Q(end__isnull=True)) ))
def add_status_query_managers(sender, **kwargs): """ Add a QueryManager for each status item dynamically. """ if not issubclass(sender, StatusModel): return # First, get current manager name... default_manager = sender._meta.default_manager for value, display in getattr(sender, 'STATUSES', ()): if _field_exists(sender, value): raise ImproperlyConfigured( "StatusModel: Model '%s' has a field named '%s' which " "conflicts with a status of the same name." % (sender.__name__, value)) sender.add_to_class(value, QueryManager(status=value)) # ...then, put it back, as add_to_class is modifying the default manager! sender._meta.default_manager_name = default_manager.name
class Bank(TimeStampedModel, SoftDeletableModel): ACCOUNT_TYPE = Choices(('Savings', _('Savings')), ('Check', _('Check'))) uuid = ShortUUIDField(max_length=8, unique=True, editable=False, verbose_name='Public identifier') account_name = models.CharField(max_length=256, blank=True) account_holder_name = models.CharField(max_length=256, blank=True) bank_name = models.CharField(max_length=256, blank=True) account_number = models.PositiveIntegerField( unique=True, validators=[MinValueValidator(10000), MaxValueValidator(999999999999)]) account_type = models.CharField(choices=ACCOUNT_TYPE, default=ACCOUNT_TYPE.Savings, max_length=20) bank_branch_code = models.PositiveIntegerField( unique=False, validators=[MinValueValidator(1000), MaxValueValidator(99999999)]) user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='bank', unique=True, on_delete=models.PROTECT) objects = SoftDeletableManager() removed = QueryManager(is_removed=True) class Meta: ordering = ['account_number'] @property def account_reference_id(self): start_index = int(str(self.account_number)[4]) account_reference = "ACC-" + str( self.account_number) + "-" + self.uuid[start_index:start_index + 4] return account_reference.upper() def __str__(self): return '({}) {}'.format(self.account_name.upper(), self.account_number)
class Advertisement(BaseModel): """The core advertisement an user creates""" owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) contact_email = models.EmailField(null=False) contact_phone = PhoneNumberField(blank=True) category = models.ForeignKey(to=Category, related_name='subcategory', on_delete=models.CASCADE) content = models.TextField(null=False) views = models.IntegerField(auto_created=True, default=0, null=False, editable=False) importance = models.IntegerField(auto_created=True, default=0, null=False) expires_date = models.DateTimeField() price = models.DecimalField(decimal_places=PRICE_MAX_DECIMALS, max_digits=PRICE_MAX_DIGITS) location_city = models.CharField(max_length=CITY_MAX_LENGTH) users_interested = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='users_interested', blank=True) published_date = models.DateTimeField(blank=True, null=True) tags = TaggableManager() objects = models.Manager() public = QueryManager(status='published').order_by('-published_date') class Meta: ordering = ['-created']
class Job(TimeStampedModel): """ A job opening. """ FULLTIME = 'ft' PARTTIME = 'pt' CONTRACT = 'co' ENGAGEMENT_TYPE_CHOICES = ((FULLTIME, 'full time'), (PARTTIME, 'part time'), (CONTRACT, 'contract')) title = models.CharField(max_length=50) slug = models.SlugField( help_text='This field is populated from the title field.') description = models.TextField(help_text='Please use Markdown format.') requirements = models.TextField(help_text='Please use Markdown format.') posted_date = models.DateField() is_expired = models.BooleanField( default=False, help_text='Check if the job has been filled. ' 'Expired jobs will not be displayed to the public.') portfolio_required = models.BooleanField(default=True) engagement_type = models.CharField(choices=ENGAGEMENT_TYPE_CHOICES, default=FULLTIME, max_length=2) objects = models.Manager() active = QueryManager(is_expired=False) class Meta: get_latest_by = 'created' ordering = ('-created', ) verbose_name_plural = 'Jobs' def __unicode__(self): return self.title @models.permalink def get_absolute_url(self): return ('jobs:detail', (), {'slug': self.slug})
class Common(models.Model): """ Абстрактный класс. Содержит `статус` и `время создания / модификации` объекта. """ DRAFT = 'draft' PUBLISHED = 'published' CHOICES_STATUS = ( (DRAFT, 'Черновик'), (PUBLISHED, 'Опубликовано'), ) status = models.CharField( 'Статус', choices=CHOICES_STATUS, default=PUBLISHED, max_length=50, ) created = models.DateTimeField( 'Дата создания', auto_now_add=True, ) modified = models.DateTimeField( 'Дата изменения', auto_now=True, ) objects = models.Manager() published = QueryManager(status=PUBLISHED) class Meta: abstract = True ordering = ('-created', )
class Act(TimeStampedModel, MonitorizedItem): """ This is the base class for all the different act types: it contains the common fields for deliberations, interrogations, interpellations, motions, agendas and emendations. It is a ``TimeStampedModel``, so it tracks creation and modification timestamps for each record. The ``related_news`` attribute can be used to fetch news related to it (or its subclasses) from ``newscache.News``. Inheritance is done through multi-table inheritance, since browsing the whole set of acts may be useful. The default manager is the ``InheritanceManager`` (from package ``django-model-utils``_), that enables the ``select_subclasses()`` method, allowing the retrieval of subclasses, when needed. .. _django-model-utils: https://bitbucket.org/carljm/django-model-utils/src """ idnum = models.CharField( max_length=64, blank=True, help_text= _("A string representing the identification number or sequence, used internally by the administration." )) title = models.CharField(_('title'), max_length=255, blank=True) adj_title = models.CharField( _('adjoint title'), max_length=255, blank=True, help_text= _("An adjoint title, added to further explain an otherwise cryptic title" )) presentation_date = models.DateField( _('presentation date'), null=True, help_text=_("Date of presentation, as stated in the act")) description = models.TextField(_('description'), blank=True) text = models.TextField(_('text'), blank=True) presenter_set = models.ManyToManyField(InstitutionCharge, blank=True, null=True, through='ActSupport', related_name='presented_act_set', verbose_name=_('presenters')) recipient_set = models.ManyToManyField(InstitutionCharge, blank=True, null=True, related_name='received_act_set', verbose_name=_('recipients')) emitting_institution = models.ForeignKey( Institution, related_name='emitted_act_set', verbose_name=_('emitting institution')) category_set = models.ManyToManyField(Category, verbose_name=_('categories'), blank=True, null=True) location_set = models.ManyToManyField(Location, through=TaggedActByLocation, verbose_name=_('locations'), blank=True, null=True) status_is_final = models.BooleanField(default=False) is_key = models.BooleanField( default=False, help_text=_("Specify whether this act should be featured")) objects = InheritanceManager() # use this manager to retrieve only key acts featured = QueryManager(is_key=True).order_by('-presentation_date') tag_set = TaggableManager(through=TaggedAct, blank=True) # manager to handle the list of news that have the act as related object related_news_set = generic.GenericRelation( News, content_type_field='related_content_type', object_id_field='related_object_pk') # manager to handle the list of monitoring having as content_object this instance monitoring_set = generic.GenericRelation(Monitoring, object_id_field='object_pk') def __unicode__(self): uc = u'%s' % (self.title, ) if self.idnum: uc = u'%s - %s' % (self.idnum, uc) if self.adj_title: uc = u'%s (%s)' % (uc, self.adj_title) return uc def downcast(self): """ Returns the "downcasted"[*]_ version of this model instance. .. [*]: In a multi-table model inheritance scenario, the term "downcasting" refers to the process to retrieve the child model instance given the parent model instance. """ # FIXME: this check is redundant, IMO (seldon) # if this method is called from a "concrete" instance # the lookup machinery either will return the instance itself # or a downcasted version of it (if any), which seems to me # the right behaviour for a ``downcast()`` method. if hasattr( self, 'act_ptr'): # method is being called from a subclass' instance return self cls = self.__class__ # ``self`` is an instance of the parent model for r in cls._meta.get_all_related_objects(): if not issubclass(r.model, cls) or\ not isinstance(r.field, models.OneToOneField): continue try: return getattr(self, r.get_accessor_name()) except models.ObjectDoesNotExist: continue @property def attachments(self): return self.attachment_set.all() @property def transitions(self): return self.transition_set.all() @property def presenters(self): return self.presenter_set.all() @property def recipients(self): return self.recipient_set.all() @property def first_signers(self): return InstitutionCharge.objects.filter( actsupport__act__id=self.pk, actsupport__support_type=ActSupport.SUPPORT_TYPE.first_signer) @property def co_signers(self): return self.presenter_set.filter( actsupport__support_type=ActSupport.SUPPORT_TYPE.co_signer) @property def tags(self): return self.tag_set.all() @property def categories(self): return self.category_set.all() @property def locations(self): return self.location_set.all() @property def act_descriptors(self): """ Returns the queryset of all those that modified the description """ return self.actdescriptor_set.all() @property def content_type_id(self): """ Returns id of the content type associated with this instance. """ return ContentType.objects.get_for_model(self).id def status(self): """ Returns the current status for the downcasted version of this act instance. Note: it seems that this method cannot be made into a property, since doing that raises a ``AttributeError: can't set attribute`` exception during Django initialization. """ return self.downcast().status @property def related_news(self): return self.related_news_set.all() def get_transitions_groups(self): """ retrieve a list of transitions grouped by status """ groups = {} this = self.downcast() if not hasattr(this, 'STATUS'): return groups # initialize all status with an empty list of transitions for status in this.STATUS: groups[status[0]] = [] # fill groups with ordered transitions for transition in this.transition_set.all().order_by( '-transition_date'): if groups.has_key(transition.final_status): groups.get(transition.final_status).append(transition) return groups def is_final_status(self, status): this = self.downcast() if not hasattr(this, 'FINAL_STATUSES'): return False for final_status in this.FINAL_STATUSES: if status == final_status[0]: return True return False def get_last_transition(self): if self.transitions: return list(self.transitions)[-1] return False
class EthereumToken(models.Model): NULL_ADDRESS = "0x0000000000000000000000000000000000000000" chain = models.ForeignKey(Chain, on_delete=models.CASCADE, related_name="tokens") code = models.CharField(max_length=8) name = models.CharField(max_length=500) decimals = models.PositiveIntegerField(default=18) address = EthereumAddressField(default=NULL_ADDRESS) objects = models.Manager() ERC20tokens = QueryManager(~Q(address=NULL_ADDRESS)) ethereum = QueryManager(address=NULL_ADDRESS) @property def is_ERC20(self) -> bool: return self.address != self.NULL_ADDRESS def __str__(self) -> str: components = [self.code] if self.is_ERC20: components.append(self.address) components.append(str(self.chain_id)) return " - ".join(components) def get_contract(self, w3: Web3) -> Contract: if not self.is_ERC20: raise ValueError("Not an ERC20 token") return w3.eth.contract(abi=EIP20_ABI, address=self.address) def build_transfer_transaction(self, w3: Web3, sender, recipient, amount: EthereumTokenAmount): chain_id = int(w3.net.version) message = f"Web3 client is on network {chain_id}, token {self.code} is on {self.chain_id}" assert self.chain_id == chain_id, message transaction_params = { "chainId": chain_id, "nonce": w3.eth.getTransactionCount(sender), "gasPrice": w3.eth.generateGasPrice(), "gas": TRANSFER_GAS_LIMIT, "from": sender, } if self.is_ERC20: transaction_params.update({ "to": self.address, "value": 0, "data": encode_transfer_data(recipient, amount) }) else: transaction_params.update({ "to": recipient, "value": amount.as_wei }) return transaction_params def _decode_transaction_data(self, tx_data, contract: Optional[Contract] = None) -> Tuple: if not self.is_ERC20: return tx_data.to, self.from_wei(tx_data.value) try: assert tx_data[ "to"] == self.address, f"Not a {self.code} transaction" assert contract is not None, f"{self.code} contract interface required to decode tx" fn, args = contract.decode_function_input(tx_data.input) # TODO: is this really the best way to identify the transaction as a value transfer? transfer_idenfifier = contract.functions.transfer.function_identifier assert transfer_idenfifier == fn.function_identifier, "No transfer transaction" return args["_to"], self.from_wei(args["_value"]) except AssertionError as exc: logger.warning(exc) return None, None except Exception as exc: logger.warning(exc) return None, None def _decode_transaction(self, transaction: Transaction) -> Tuple: # A transfer transaction input is 'function,address,uint256' # i.e, 16 bytes + 20 bytes + 32 bytes = hex string of length 136 try: # transaction input strings are '0x', so we they should be 138 chars long assert len( transaction.data) == 138, "Not a ERC20 transfer transaction" assert transaction.logs.count( ) == 1, "Transaction does not contain log changes" recipient_address = to_checksum_address(transaction.data[-104:-64]) wei_transferred = int(transaction.data[-64:], 16) tx_log = transaction.logs.first() assert int( tx_log.data, 16) == wei_transferred, "Log data and tx amount do not match" return recipient_address, self.from_wei(wei_transferred) except AssertionError as exc: logger.info(f"Failed to get transfer data from transaction: {exc}") return None, None except ValueError: logger.info( f"Failed to extract transfer amounts from {transaction.hash.hex()}" ) return None, None except Exception as exc: logger.exception(exc) return None, None def from_wei(self, wei_amount: int) -> EthereumTokenAmount: value = Decimal(wei_amount) / (10**self.decimals) return EthereumTokenAmount(amount=value, currency=self) @staticmethod def ETH(chain: Chain): eth, _ = EthereumToken.objects.get_or_create( chain=chain, code="ETH", defaults={"name": "Ethereum"}) return eth @classmethod def make(cls, address: str, chain: Chain, **defaults): if address == EthereumToken.NULL_ADDRESS: return EthereumToken.ETH(chain) obj, _ = cls.objects.update_or_create(address=address, chain=chain, defaults=defaults) return obj class Meta: unique_together = (("chain", "address"), )
class Wallet(TimeStampedModel, SoftDeletableModel): bank = models.OneToOneField('wallet.Bank', related_name='wallet', unique=True, on_delete=models.PROTECT) objects = SoftDeletableManager() removed = QueryManager(is_removed=True) class Meta: ordering = ['bank'] permissions = (('can_view_wallet_report', 'Can view wallet report'), ) @staticmethod def validate_amount(amount): if not isinstance(amount, (int, Decimal, str)): raise ValueError('Amount need to be a string, integer or Decimal.') return Decimal(amount) def register_income(self, amount, reference): amount = self.validate_amount(amount) if amount <= Decimal('0'): raise ValueError('Amount need to be positive number, not %s' % amount) return self._create_transaction(value, reference) def _create_transaction(self, amount, reference): return Transaction.objects.create(wallet=self, reference=reference, amount=amount) def register_expense(self, amount, reference, current_lock=None): amount = self.validate_amount(amount) if amount >= Decimal('0'): raise ValueError('Amount need to be negative number, not %s' % amount) if current_lock is not None: if current_lock.wallet != self: raise ValueError('Lock not for this wallet!') if self.balance >= amount: return self._create_transaction(amount, reference) else: raise WalletHasInsufficientFunds( 'Not insufficient funds to spend %s' % amount) with Locked(self): if self.balance >= amount: return self._create_transaction(amount, reference) else: raise WalletHasInsufficientFunds( 'Not insufficient funds to spend %s' % amount) @property def balance(self): return self.transactions.aggregate( Sum('transaction_amount'))['transaction_amount__sum'] def __str__(self): return '({})'.format(self.bank)
class Project(OrderedModel): """ A project completed by the agency. """ STATUS = Choices( ('draft', _('Draft')), ('published', _('Published')) ) name = models.CharField( max_length=200, help_text='Limited to 200 characters.', default='' ) slug = models.SlugField( help_text='Used to build the project URL. \ Will populate from the name field.', default='', unique=True ) description = models.TextField( default='' ) categories = models.ManyToManyField( Category, blank=True, default='', help_text='Optional. Used for administrative purposes only. Not \ shown to the public.' ) hero_image = ImageField( upload_to='projects/hero_images', default='', help_text='Please use jpg (jpeg) or png files only. Will be resized \ for public display.', validators=[validate_file_type] ) is_featured = models.BooleanField( default=False, help_text='Check this box to feature this project on the homepage \ and project list page.', ) status = StatusField(default='draft') objects = models.Manager() published = QueryManager(status='published') featured = QueryManager(Q(is_featured=True) & Q(status='published')) class Meta(OrderedModel.Meta): pass def __str__(self): return self.name def get_absolute_url(self): return reverse('project_detail', kwargs={'slug': self.slug}) def get_next(self): """ Method to return the next object by order field. """ next = self.__class__.objects.filter(order__gt=self.order) try: return next[0].get_absolute_url() except IndexError: return False def get_previous(self): """ Method to return the previous object by order field. """ previous = self.__class__.objects.filter(order__lt=self.order) try: return previous[0].get_absolute_url() except IndexError: return False
class Attribute(models.Model): label = models.CharField(verbose_name=_('label'), max_length=63, unique=True) description = models.TextField(verbose_name=_('description'), blank=True) name = models.SlugField(verbose_name=_('name'), max_length=256, unique=True) required = models.BooleanField(verbose_name=_('required'), blank=True, default=False) asked_on_registration = models.BooleanField( verbose_name=_('asked on registration'), blank=True, default=False) user_editable = models.BooleanField(verbose_name=_('user editable'), blank=True, default=False) user_visible = models.BooleanField(verbose_name=_('user visible'), blank=True, default=False) multiple = models.BooleanField(verbose_name=_('multiple'), blank=True, default=False) kind = models.CharField(max_length=16, verbose_name=_('kind')) disabled = models.BooleanField(verbose_name=_('disabled'), blank=True, default=False) searchable = models.BooleanField(verbose_name=_('searchable'), blank=True, default=False) scopes = models.CharField(verbose_name=_('scopes'), help_text=_('scopes separated by spaces'), blank=True, default='', max_length=256) order = models.PositiveIntegerField(verbose_name=_('order'), default=0) objects = managers.AttributeManager(disabled=False) all_objects = managers.AttributeManager() registration_attributes = QueryManager(asked_on_registration=True) user_attributes = QueryManager(user_editable=True) def get_form_field(self, **kwargs): from . import attribute_kinds kwargs['label'] = self.label kwargs['required'] = self.required if self.description: kwargs['help_text'] = self.description return attribute_kinds.get_form_field(self.kind, **kwargs) def get_kind(self): from . import attribute_kinds return attribute_kinds.get_kind(self.kind) def contribute_to_form(self, form, **kwargs): form.fields[self.name] = self.get_form_field(**kwargs) def get_value(self, owner, verified=None): kind = self.get_kind() deserialize = kind['deserialize'] atvs = AttributeValue.objects.with_owner(owner) if verified is True or verified is False: atvs = atvs.filter(verified=verified) if self.multiple: result = [] for atv in atvs.filter(attribute=self, multiple=True): result.append(deserialize(atv.content)) return result else: try: atv = atvs.get(attribute=self, multiple=False) return deserialize(atv.content) except AttributeValue.DoesNotExist: return kind['default'] def set_value(self, owner, value, verified=False): serialize = self.get_kind()['serialize'] # setting to None is to delete if value is None: AttributeValue.objects.with_owner(owner).filter( attribute=self).delete() return if self.multiple: assert isinstance(value, (list, set, tuple)) values = value for value in values: content = serialize(value) av, created = AttributeValue.objects.get_or_create( content_type=ContentType.objects.get_for_model(owner), object_id=owner.pk, attribute=self, multiple=True, content=content, defaults={'verified': verified}) if not created: av.verified = verified av.save() else: content = serialize(value) av, created = AttributeValue.objects.get_or_create( content_type=ContentType.objects.get_for_model(owner), object_id=owner.pk, attribute=self, multiple=False, defaults={ 'content': content, 'verified': verified }) if not created: av.content = content av.verified = verified av.save() # if owner has a modified field, update it try: modified = owner.__class__._meta.get_field('modified') except FieldDoesNotExist: pass else: if getattr(modified, 'auto_now', False): owner.save(update_fields=['modified']) def natural_key(self): return (self.name, ) def __unicode__(self): return self.label class Meta: verbose_name = _('attribute definition') verbose_name_plural = _('attribute definitions') ordering = ('order', 'id')
class Votation(models.Model): """ This model stores information about a single ballot. A ballot has an outcome determined by the sum of the votes of the participants and the legal number of the ballot itself. The latter depend on the type of ballot. """ OUTCOMES = Choices( (0, 'No Esito'), (1, 'Approvato'), (2, 'Respinto'), (3, 'SI Numero Legale'), (4, 'NO Numero Legale'), (5, 'Annullata'), ) idnum = models.CharField(blank=True, max_length=64) sitting = models.ForeignKey(Sitting, blank=False, null=False) act = models.ForeignKey(Act, null=True) # this field is used to keep the textual description of the related act # as expressed in the voting system act_descr = models.CharField(blank=True, max_length=255) group_set = models.ManyToManyField(Group, through='GroupVote') charge_set = models.ManyToManyField(InstitutionCharge, through='ChargeVote') n_legal = models.IntegerField(default=0) n_presents = models.IntegerField(default=0) n_partecipants = models.IntegerField(default=0) n_absents = models.IntegerField(default=0) n_yes = models.IntegerField(default=0) n_no = models.IntegerField(default=0) n_abst = models.IntegerField(default=0) n_maj = models.IntegerField(default=0) outcome = models.IntegerField(choices=OUTCOMES, blank=True, null=True) is_key = models.BooleanField( default=False, help_text=_("Specify whether this is a key votation")) n_rebels = models.IntegerField(default=0) slug = models.SlugField(max_length=500, blank=True, null=True) datetime = models.DateTimeField(blank=True, null=True, verbose_name=_("datetime")) source_url = models.URLField( max_length=500, blank=True, null=True, help_text=_( "If the object has been imported from a public URL, report it here" )) # default manager must be explicitly defined, when # at least another manager is present objects = models.Manager() # use this manager to retrieve only key votations key = QueryManager(is_key=True).order_by('-sitting__date') # use this manager to retrieve only linked acts is_linked_to_act = QueryManager(act__isnull=False) # activation of the ``is_linked_filter`` # add ``act`` to the ``list_filter`` list in ``admin.py`` # to filter votations based on the existence of a related act act.is_linked_filter = True @property def is_key_yesno(self): if self.is_key: return _('yes') else: return _('no') def save(self, *args, **kwargs): """ This method takes care of setting a default slug for Votations that are linked to a Sitting. This transparently helps the slug field for most of the "normal" use cases of Votation. """ if not self.slug: self.slug = self.get_default_slug() super(Votation, self).save(*args, **kwargs) @property def date(self): return self.sitting.date def get_default_slug(self): """ This method will be used for assigning a default slug to a Votation that does not have one. """ if self.sitting and self.idnum: cleaned_idnum = re.sub(r'[^\w\d]+', '-', self.idnum) slug = slugify("%s-%s" % (self.sitting.date.isoformat(), cleaned_idnum)) return slug[:100] else: raise ValueError( "In order to compute the default slug, the Votation should be linked to a Sitting" ) class Meta: verbose_name = _('votation') verbose_name_plural = _('votations') unique_together = (('slug', ), ( 'sitting', 'idnum', )) def __unicode__(self): return _('Votation %(idnum)s') % { "idnum": self.idnum, } @models.permalink def get_absolute_url(self): """ Introduce url based on slugs. To keen retro-compatibility during introduction of slugs, it also allows to view the old url using pk. """ if getattr(self, "slug", None) and self.slug: return ("om_votation_detail", (), {'slug': self.slug}) else: return ("om_votation_detail", (), {'pk': self.pk}) @property def group_votes(self): return self.groupvote_set.all() @property def charge_votes(self): return self.chargevote_set.all() @property def charge_rebel_votes(self): return self.chargevote_set.filter(is_rebel=True) @property def transitions(self): return self.transition_set.all() @property def ref_act(self): act = None if self.act: act = self.act # elif self.transitions.count() > 0: else: try: act = self.transitions[0].act except IndexError: # self.transitions is empty pass return act @property def is_linked(self): return self.ref_act is not None @property def is_secret(self): """ A votation is secret if there is at least one voter with a secret vote (there could be voters that casted differnt types of votes, e.g. absent or abstained) """ return self.charge_votes.filter( vote=ChargeVote.VOTES.secret).count() > 0 @property def charges_count(self): return self.chargevote_set.filter(charge__can_vote=True).count() def update_presence_caches(self): """ update presence caches for each voting charge of this votation """ for vc in self.charge_votes: vc.charge.update_presence_cache() def verify_integrity(self): """ Verify the integrity of the ballot structure. In particular checks that related self.votes are consistent with the votes counted in the self.n_* fields. If an error is detected, raise an exception explaining the problem """ errors = [] # check legal number is greater than 0 if self.n_legal < 0: errors.append( "The legal number should always be positive. Passed: %s" % self.n_legal) # count the number of presents is consistent with yes + no + abstained n_sum = self.n_yes + self.n_no + self.n_abst if n_sum > self.n_presents: errors.append( "The number of presents (%s) is smaller than the sum of yes (%s), no (%s) and abstained (%s): %s. Additional info: absents = %s, rebels = %s" % (self.n_presents, self.n_yes, self.n_no, self.n_abst, n_sum, self.n_absents, self.n_rebels)) num_charge_votes = self.charge_votes.count() if num_charge_votes < self.n_presents: errors.append( "The related votes (%s) are less that the reported number of presents (%s)" % ( num_charge_votes, self.n_presents, )) if num_charge_votes < n_sum: errors.append( "The number of related votes (%s) is less than the yes, no and abst votes (%s)" % ( num_charge_votes, n_sum, )) # check the number of presents is greater than the legal number if self.n_presents < self.n_legal: errors.append( "Number of presents (%s) should not be less than legal number (%s)" % (self.n_presents, self.n_legal)) if len(errors) > 0: raise Exception(",".join(errors)) @property def majority_vs_minority(self): count = {'majority': Counter(), 'minority': Counter()} for cv in self.chargevote_set.all(): if (not cv.charge_group_at_vote_date or cv.charge_group_at_vote_date.is_majority_now): count['majority'].update({cv.vote: 1}) else: count['minority'].update({cv.vote: 1}) return dict(count)