class AutomaticUrl2(Model): foo = ForeignKey(AutomaticUrl, on_delete=CASCADE) class Meta: ordering = ('pk',)
class Comment(models.Model): """ This model represents a comment which can be added to any post by an anonymous visitor """ name = CharField(max_length=250) email = models.EmailField() content = TextField(max_length=2000, blank=True) publishing_date = DateTimeField(default=timezone.now) # These two will not be settable manually. # The "hash_id" is an integer hash value which is computed from the name of the comment (the pseudonym the author # chooses) and the entered email address. # In the first step, the author field can only be manipulated from the admin backend which means that only # registered admins could change this. hash_id = models.CharField(max_length=3, null=True, blank=True) author = ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True) # https://docs.djangoproject.com/en/3.2/ref/contrib/contenttypes/x content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() entry = GenericForeignKey('content_type', 'object_id') # This is intended to at some point help with moderating. The idea is to have a filter check for obvious problems # with the comments like swearing etc. and then automatically set active to false, which means they wont be # displayed and wait for further approval in the admin backend. active = BooleanField(default=False) # So here are my thoughts about images: I think anonymous commenters pose quite the problem: Beside the usual stuff # like spamming etc. I am worried about identity theft: On default someone could just copy the comment name of # someone else and pretend that it is the same person. So the plan is to also have the commenter enter the email # address for every comment and then build a hash value from the public comment name and the email address which is # only saved on the server. Based on this hash value (there will be relatively few possible values) a specific Anon # profile picture will be set. This will ensure that the same person could be identified by the profile picture. image = FilerImageField(on_delete=models.CASCADE, null=True, blank=True) @property def html(self): return self.content.replace('\n', '<br>') def save(self, *args, **kwargs): if not self.hash_id: self.hash_id = self.generate_hash_id() return super(Comment, self).save(*args, **kwargs) def get_image_url(self): if self.author: return self.author.image.url image_file = f'comment_images/{self.hash_id}.jpg' return static(image_file) def generate_hash_id(self) -> str: hash_base = f'{self.name} - {self.email.lower()}' hash_string = str(hash(hash_base)) hash_id = hash_string[-3:] return hash_id def get_shortened_content(self, length=30) -> str: """ Returns the first *length* characters of the content string of the comment. If the comment is shorter than this length, returns the whole content instead. :param length: The int amount of the first characters of the content to return :return: """ if len(self.content) <= length: return self.content else: shortened_content = self.content[0:length] + '...' return shortened_content # This is curious: We assign a property "short_description" to the method instance. This is made to support the # usage of this method in the admin form list display. If the function has this "short description" string property # then this string will be displayed as the title of the column in the list display. get_shortened_content.short_description = 'Content'
class App(TranslatableModel): objects = AppManager() id = CharField(max_length=256, unique=True, primary_key=True, verbose_name=_('ID'), help_text=_('app ID, identical to folder name')) categories = ManyToManyField('Category', verbose_name=_('Category')) translations = TranslatedFields( name=CharField(max_length=256, verbose_name=_('Name'), help_text=_('Rendered app name for users')), summary=CharField(max_length=256, verbose_name=_('Summary'), help_text=_( 'Short text describing the app\'s purpose')), description=TextField(verbose_name=_('Description'), help_text=_( 'Will be rendered as Markdown')) ) # resources user_docs = URLField(max_length=256, blank=True, verbose_name=_('User documentation URL')) admin_docs = URLField(max_length=256, blank=True, verbose_name=_('Admin documentation URL')) developer_docs = URLField(max_length=256, blank=True, verbose_name=_('Developer documentation URL')) issue_tracker = URLField(max_length=256, blank=True, verbose_name=_('Issue tracker URL')) website = URLField(max_length=256, blank=True, verbose_name=_('Homepage')) created = DateTimeField(auto_now_add=True, editable=False, verbose_name=_('Created at')) last_modified = DateTimeField(auto_now=True, editable=False, db_index=True, verbose_name=_('Updated at')) owner = ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('App owner'), on_delete=CASCADE, related_name='owned_apps') co_maintainers = ManyToManyField(settings.AUTH_USER_MODEL, blank=True, verbose_name=_('Co-Maintainers'), related_name='co_maintained_apps') authors = ManyToManyField('AppAuthor', blank=True, related_name='apps', verbose_name=_('App authors')) is_featured = BooleanField(verbose_name=_('Featured'), default=False) rating_recent = FloatField(verbose_name=_('Recent rating'), default=0.5) rating_overall = FloatField(verbose_name=_('Overall rating'), default=0.5) rating_num_recent = IntegerField( verbose_name=_('Number of recently submitted ratings'), default=0) rating_num_overall = IntegerField( verbose_name=_('Number of overall submitted ratings'), default=0) last_release = DateTimeField(editable=False, db_index=True, verbose_name=_('Last release at'), default=timezone.now) certificate = TextField(verbose_name=_('Certificate')) ownership_transfer_enabled = BooleanField( verbose_name=_('Ownership transfer enabled'), default=False, help_text=_('If enabled, a user can try to register the same app ' 'again using the public certificate and signature. If he ' 'does, the app will be transferred to him.')) class Meta: verbose_name = _('App') verbose_name_plural = _('Apps') def __str__(self) -> str: return self.name def can_update(self, user: User) -> bool: return self.owner == user or user in self.co_maintainers.all() def can_delete(self, user: User) -> bool: return self.owner == user def _get_grouped_releases(self, get_release_func): releases = NextcloudRelease.objects.all() versions = map(lambda r: r.version, releases) compatible_releases = map(lambda v: (v, get_release_func(v)), versions) grouped_releases = group_by_main_version(dict(compatible_releases)) # deduplicate releases result = {} for version, releases in grouped_releases.items(): result[version] = list(distinct(releases, lambda r: r.version)) return result def releases_by_platform_v(self): """Looks up all compatible stable releases for each platform version. Example of returned dict: {'9.1': [<AppRelease object>, <AppRelease object>], '9.0': [<AppRelease object>]} :return dict with all compatible stable releases for each platform version. """ return self._get_grouped_releases(self.compatible_releases) def unstable_releases_by_platform_v(self): """Looks up all compatible unstable releases for each platform version. Example of returned dict: {'9.1': [<AppRelease object>, <AppRelease object>], '9.0': [<AppRelease object>]} :return dict with all compatible unstable releases for each platform version. """ return self._get_grouped_releases(self.compatible_unstable_releases) def latest_releases_by_platform_v(self): """Looks up the latest stable and unstable release for each platform version. Example of returned dict: {'9.1': { 'stable': <AppRelease object>, 'unstable': <AppRelease object> }, '9.0': { 'stable': <AppRelease object> }} :return dict with the latest stable and unstable release for each platform version. """ stable = self.releases_by_platform_v() unstable = self.unstable_releases_by_platform_v() def filter_latest(pair): version, releases = pair return (version, self._latest(releases)) latest_stable = dict(map(filter_latest, stable.items())) latest_unstable = dict(map(filter_latest, unstable.items())) all_versions = set(chain(latest_stable.keys(), latest_unstable.keys())) def stable_or_unstable_releases(ver): return (ver, { 'stable': latest_stable.get(ver, None), 'unstable': latest_unstable.get(ver, None) }) return dict(map(stable_or_unstable_releases, all_versions)) def compatible_releases(self, platform_version, inclusive=True): """Returns all stable releases of this app that are compatible with the given platform version. :param inclusive: Use inclusive version check (see AppRelease.is_compatible()). :return a sorted list of all compatible stable releases. """ return sorted( filter( lambda r: r.is_compatible(platform_version, inclusive) and not r.is_unstable, self.releases.all()), key=lambda r: AppSemVer(r.version, r.is_nightly, r.last_modified), reverse=True) def compatible_unstable_releases(self, platform_version, inclusive=True): """Returns all unstable releases of this app that are compatible with the given platform version. :param inclusive: Use inclusive version check (see AppRelease.is_compatible()). :return a sorted list of all compatible unstable releases. """ return sorted( filter( lambda r: r.is_compatible(platform_version, inclusive) and r.is_unstable, self.releases.all()), key=lambda r: AppSemVer(r.version, r.is_nightly, r.last_modified), reverse=True) def _latest(self, releases): try: return max(releases, key=lambda r: AppSemVer(r.version, r.is_nightly, r.last_modified)) except ValueError: return None def save(self, *args, **kwargs): # If the certificate has changed, delete all releases. try: if self.pk is not None: orig = App.objects.get(pk=self.pk) current = self.certificate.replace('\r', '').strip() former = orig.certificate.replace('\r', '').strip() # for some reason the django admin inserts \r\n for \n so # saving a model in the admin with the same cert kills all # releases if current != former: self.releases.all().delete() except self.DoesNotExist: pass super().save(*args, **kwargs)
class BMC(CleanSave, TimestampedModel): """A `BMC` represents an existing 'baseboard management controller'. For practical purposes in MAAS, this is any addressable device that can control the power state of Nodes. The BMC associated with a Node is the one expected to control its power. Power parameters that apply to all nodes controlled by a BMC are stored here in the BMC. Those that are specific to different Nodes on the same BMC are stored in the Node model instances. :ivar ip_address: This `BMC`'s IP Address. :ivar power_type: The power type defines which type of BMC this is. Its value must match a power driver class name. :ivar power_parameters: Some JSON containing arbitrary parameters this BMC's power driver requires to function. :ivar objects: The :class:`BMCManager`. """ class Meta(DefaultMeta): unique_together = ("power_type", "power_parameters", "ip_address") objects = Manager() bmcs = BMCManager() bmc_type = IntegerField(choices=BMC_TYPE_CHOICES, editable=False, default=BMC_TYPE.DEFAULT) ip_address = ForeignKey(StaticIPAddress, default=None, blank=True, null=True, editable=False, on_delete=SET_NULL) # The possible choices for this field depend on the power types advertised # by the rack controllers. This needs to be populated on the fly, in # forms.py, each time the form to edit a node is instantiated. power_type = CharField(max_length=10, null=False, blank=True, default='') # JSON-encoded set of parameters for power control, limited to 32kiB when # encoded as JSON. These apply to all Nodes controlled by this BMC. power_parameters = JSONObjectField(max_length=(2**15), blank=True, default='') # Rack controllers that have access to the BMC by routing instead of # having direct layer 2 access. routable_rack_controllers = ManyToManyField( "RackController", blank=True, editable=True, through="BMCRoutableRackControllerRelationship", related_name="routable_bmcs") # Values for Pod's. # 1. Name of the Pod. # 2. List of architectures that a Pod supports. # 3. Capabilities that the Pod supports. # 4. Total cores in the Pod. # 5. Fastest CPU speed in the Pod. # 6. Total amount of memory in the Pod. # 7. Total amount in bytes of local storage available in the Pod. # 8. Total number of available local disks in the Pod. # 9. The resource pool machines in the pod should belong to by default. # 10. The zone of the Pod. # 11. The tags of the Pod. # 12. CPU over commit ratio multiplier ('over_commit' capabilities). # 13. Memory over commit ratio multiplier ('over_commit' capabilities). name = CharField(max_length=255, default='', blank=True, unique=True) architectures = ArrayField(TextField(), blank=True, null=True, default=list) capabilities = ArrayField(TextField(), blank=True, null=True, default=list) cores = IntegerField(blank=False, null=False, default=0) cpu_speed = IntegerField(blank=False, null=False, default=0) # MHz memory = IntegerField(blank=False, null=False, default=0) local_storage = BigIntegerField( # Bytes blank=False, null=False, default=0) local_disks = IntegerField(blank=False, null=False, default=-1) iscsi_storage = BigIntegerField( # Bytes blank=False, null=False, default=-1) default_pool = ForeignKey(ResourcePool, default=None, null=True, blank=True, editable=True, on_delete=PROTECT) zone = ForeignKey(Zone, verbose_name="Physical zone", default=get_default_zone, editable=True, db_index=True, on_delete=SET_DEFAULT) tags = ArrayField(TextField(), blank=True, null=True, default=list) cpu_over_commit_ratio = FloatField(default=1, validators=[MinValueValidator(0)]) memory_over_commit_ratio = FloatField(default=1, validators=[MinValueValidator(0)]) def __str__(self): return "%s (%s)" % (self.id, self.ip_address if self.ip_address else "No IP") def _as(self, model): """Create a `model` that shares underlying storage with `self`. In other words, the newly returned object will be an instance of `model` and its `__dict__` will be `self.__dict__`. Not a copy, but a reference to, so that changes to one will be reflected in the other. """ new = object.__new__(model) new.__dict__ = self.__dict__ return new def as_bmc(self): """Return a reference to self that behaves as a `BMC`.""" return self._as(BMC) def as_pod(self): """Return a reference to self that behaves as a `Pod`.""" return self._as(Pod) _as_self = { BMC_TYPE.BMC: as_bmc, BMC_TYPE.POD: as_pod, } def as_self(self): """Return a reference to self that behaves as its own type.""" return self._as_self[self.bmc_type](self) def delete(self): """Delete this BMC.""" maaslog.info("%s: Deleting BMC", self) super(BMC, self).delete() def save(self, *args, **kwargs): """Save this BMC.""" super(BMC, self).save(*args, **kwargs) # We let name be blank for the initial save, but fix it before the # save completes. This is because set_random_name() operates by # trying to re-save the BMC with a random hostname, and retrying until # there is no conflict. if self.name == '': self.set_random_name() def set_random_name(self): """Set a random `name`.""" while True: self.name = petname.Generate(2, "-") try: self.save() except ValidationError: pass else: break def clean(self): """ Update our ip_address if the address extracted from our power parameters has changed. """ new_ip = BMC.extract_ip_address(self.power_type, self.power_parameters) current_ip = None if self.ip_address is None else self.ip_address.ip # Set the ip_address field. If we have a bracketed address, assume # it's IPv6, and strip the brackets. if new_ip and new_ip.startswith('[') and new_ip.endswith(']'): new_ip = new_ip[1:-1] if new_ip != current_ip: if new_ip is None: self.ip_address = None else: # Update or create a StaticIPAddress for the new IP. try: # This atomic block ensures that an exception within will # roll back only this block's DB changes. This allows us to # swallow exceptions in here and keep all changes made # before or after this block is executed. with transaction.atomic(): subnet = Subnet.objects.get_best_subnet_for_ip(new_ip) (self.ip_address, _) = StaticIPAddress.objects.get_or_create( ip=new_ip, defaults={ 'alloc_type': IPADDRESS_TYPE.STICKY, 'subnet': subnet, }) except Exception as error: maaslog.info( "BMC could not save extracted IP " "address '%s': '%s'", new_ip, error) @staticmethod def scope_power_parameters(power_type, power_params): """Separate the global, bmc related power_parameters from the local, node-specific ones.""" if not power_type: # If there is no power type, treat all params as node params. return ({}, power_params) power_driver = PowerDriverRegistry.get_item(power_type) if power_driver is None: # If there is no power driver, treat all params as node params. return ({}, power_params) power_fields = power_driver.settings if not power_fields: # If there is no parameter info, treat all params as node params. return ({}, power_params) bmc_params = {} node_params = {} for param_name in power_params: power_field = power_driver.get_setting(param_name) if (power_field and power_field.get('scope') == SETTING_SCOPE.BMC): bmc_params[param_name] = power_params[param_name] else: node_params[param_name] = power_params[param_name] return (bmc_params, node_params) @staticmethod def extract_ip_address(power_type, power_parameters): """ Extract the ip_address from the power_parameters. If there is no power_type, no power_parameters, or no valid value provided in the power_address field, returns None. """ if not power_type or not power_parameters: # Nothing to extract. return None power_driver = PowerDriverRegistry.get_item(power_type) if power_driver is None: maaslog.warning("No power driver for power type %s" % power_type) return None power_type_parameters = power_driver.settings if not power_type_parameters: maaslog.warning("No power driver settings for power type %s" % power_type) return None ip_extractor = power_driver.ip_extractor if not ip_extractor: maaslog.info("No IP extractor configured for power type %s. " "IP will not be extracted." % power_type) return None field_value = power_parameters.get(ip_extractor.get('field_name')) if not field_value: maaslog.warning("IP extractor field_value missing for %s" % power_type) return None extraction_pattern = ip_extractor.get('pattern') if not extraction_pattern: maaslog.warning("IP extractor extraction_pattern missing for %s" % power_type) return None match = re.match(extraction_pattern, field_value) if match: return match.group('address') # no match found - return None return None def get_layer2_usable_rack_controllers(self, with_connection=True): """Return a list of `RackController`'s that have the ability to access this `BMC` directly through a layer 2 connection.""" ip_address = self.ip_address if ip_address is None or ip_address.ip is None or ip_address.ip == '': return set() # The BMC has a valid StaticIPAddress set. Make sure that the subnet # is correct for that BMC. subnet = Subnet.objects.get_best_subnet_for_ip(ip_address.ip) if subnet is not None and self.ip_address.subnet_id != subnet.id: self.ip_address.subnet = subnet self.ip_address.save() # Circular imports. from maasserver.models.node import RackController return RackController.objects.filter_by_url_accessible( ip_address.ip, with_connection=with_connection) def get_routable_usable_rack_controllers(self, with_connection=True): """Return a list of `RackController`'s that have the ability to access this `BMC` through a route on the rack controller.""" routable_racks = [ relationship.rack_controller for relationship in (self.routable_rack_relationships.all( ).select_related("rack_controller")) if relationship.routable ] if with_connection: conn_rack_ids = [client.ident for client in getAllClients()] return [ rack for rack in routable_racks if rack.system_id in conn_rack_ids ] else: return routable_racks def get_usable_rack_controllers(self, with_connection=True): """Return a list of `RackController`'s that have the ability to access this `BMC` either using layer2 or routable if no layer2 are available. """ racks = self.get_layer2_usable_rack_controllers( with_connection=with_connection) if len(racks) == 0: # No layer2 routable rack controllers. Use routable rack # controllers. racks = self.get_routable_usable_rack_controllers( with_connection=with_connection) return racks def get_client_identifiers(self): """Return a list of identifiers that can be used to get the `rpc.common.Client` for this `BMC`. :raise NoBMCAccessError: Raised when no rack controllers have access to this `BMC`. """ rack_controllers = self.get_usable_rack_controllers() identifers = [controller.system_id for controller in rack_controllers] return identifers def is_accessible(self): """If the BMC is accessible by at least one rack controller.""" racks = self.get_usable_rack_controllers(with_connection=False) return len(racks) > 0 def update_routable_racks(self, routable_racks_ids, non_routable_racks_ids): """Set the `routable_rack_controllers` relationship to the new information.""" BMCRoutableRackControllerRelationship.objects.filter( bmc=self.as_bmc()).delete() self._create_racks_relationship(routable_racks_ids, True) self._create_racks_relationship(non_routable_racks_ids, False) def _create_racks_relationship(self, rack_ids, routable): """Create `BMCRoutableRackControllerRelationship` for list of `rack_ids` and wether they are `routable`.""" # Circular imports. from maasserver.models.node import RackController for rack_id in rack_ids: try: rack = RackController.objects.get(system_id=rack_id) except RackController.DoesNotExist: # Possible it was delete before this call, but very very rare. pass BMCRoutableRackControllerRelationship(bmc=self, rack_controller=rack, routable=routable).save()
class Step(Model): number = PositiveSmallIntegerField() description = TextField() recipe = ForeignKey(Recipe, on_delete=CASCADE)
class Report(Model): reporter = ForeignKey('Writer', on_delete=CASCADE) article = ForeignKey('Article', on_delete=CASCADE)
class Answer(Model): question = ForeignKey("TestQuestion", on_delete=CASCADE, related_name="answers") text = TextField() is_correct = BooleanField(default=False)
class EventSpeaker(Orderable): event = ParentalKey("Event", related_name="speaker") speaker = ForeignKey("people.Person", on_delete=CASCADE, related_name="+") panels = [PageChooserPanel("speaker")]
class Transaction(StatefulTransactionModel): # user who participate in this transaction # to_u = ForeignKey( # User, on_delete=models.PROTECT, related_name="to_user") # we can assume every transaction has a bill bill = ForeignKey(Bill, on_delete=models.CASCADE) @property def owner(self): # is the the bill owner can setup this transactions return self.bill.owner def approve(self): """ @pre: request user == self.from_u """ if self.state == PREPARE: # only the prepare state can go be approved self.state = APPROVED self.save() return True return False def reject(self): """ @pre: request_uesr = self.from_u """ # push this transaction to reject state if self.state == PREPARE: self.state = REJECTED self.save() return True return False def force_reject(self): """ @pre: request_user = self.owner """ if self.state == PREPARE or self.state == APPROVED: self.state = REJECTED self.save() return True return False def resume(self): if self.state in [REJECTED, SUSPEND]: self.state = PREPARE self.save() return True return False def commit(self): """ @pre self.state == CONCENCUS """ self.state = COMMITED self.save() def revoke(self): """ @pre self.state == COMMITED """ self.state = CONCENCUS self.save() def __str__(self): return "From %s to %s $%f in bill %d with state %s" % \ (self.from_u.username, self.to_u.username, self.amount, self.bill.id, self.state) @classmethod def get_balance(cls, user, settle=None): """ @post: settle== None ==> return == balance with all unfinished and concus bill @post: settle!= None ==> return == balance with all unfinished and concus bill and bill == settle """ # construct the basic query query = cls.objects if settle != None: # update the query and filter # filter by the given settle query = query.filter(bill__settlement=settle).filter( Q(state=COMMITED) | Q(state=FINISH)) else: query = query.filter( Q(state=COMMITED) | Q(state=CONCENCUS)) # calculate the given user's balance # Coalesce is to provide a 0 when the none type is occour return query.filter(to_u=user).distinct().aggregate( asum=Coalesce(Sum('amount'), Value(0)) )['asum']\ - query.filter(from_u=user).distinct().aggregate( asum=Coalesce(Sum('amount'), Value(0)) )['asum'] @classmethod def get_gdp_by_settlement(cls, settle): return cls.get_trs_by_settlement(settle).aggregate( asum=Coalesce(Sum('amount'), Value(0)) )['asum'] @classmethod def get_trs_by_settlement(cls, settle): return cls.objects.filter(bill__settlement=settle) @classmethod def filter_user(cls, user): return cls.objects.filter( Q(to_u=user) | Q(from_u=user) ) @classmethod def get_waitng_tr(cls, user): """ Find all the waiting decition trancation by this user """ return cls.filter_user(user).filter(state=PREPARE)
class Event(BasePage): resource_type = "event" parent_page_types = ["events.Events"] subpage_types = [] template = "event.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) body = CustomStreamField( blank=True, null=True, help_text=( "Optional body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ), ) agenda = StreamField( StreamBlock([("agenda_item", AgendaItemBlock())], required=False), blank=True, null=True, help_text="Optional list of agenda items for this event", ) speakers = StreamField( StreamBlock( [ ("speaker", PageChooserBlock(target_model="people.Person")), ("external_speaker", ExternalSpeakerBlock()), ], required=False, ), blank=True, null=True, help_text="Optional list of speakers for this event", ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta fields start_date = DateField(default=datetime.date.today) end_date = DateField(blank=True, null=True) latitude = FloatField(blank=True, null=True) longitude = FloatField(blank=True, null=True) register_url = URLField("Register URL", blank=True, null=True) venue_name = CharField(max_length=100, blank=True, default="") venue_url = URLField("Venue URL", max_length=100, blank=True, default="") address_line_1 = CharField(max_length=100, blank=True, default="") address_line_2 = CharField(max_length=100, blank=True, default="") address_line_3 = CharField(max_length=100, blank=True, default="") city = CharField(max_length=100, blank=True, default="") state = CharField("State/Province/Region", max_length=100, blank=True, default="") zip_code = CharField("Zip/Postal code", max_length=100, blank=True, default="") country = CountryField(blank=True, default="") keywords = ClusterTaggableManager(through=EventTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), MultiFieldPanel( [ImageChooserPanel("image")], heading="Image", help_text=( "Optional header image. If not specified a fallback will be used. " "This image is also shown when sharing this page via social media" ), ), StreamFieldPanel("body"), StreamFieldPanel("agenda"), StreamFieldPanel("speakers"), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ MultiFieldPanel( [ FieldPanel("start_date"), FieldPanel("end_date"), FieldPanel("latitude"), FieldPanel("longitude"), FieldPanel("register_url"), ], heading="Event details", classname="collapsible", help_text=mark_safe( "Optional time and location information for this event. Latitude and " "longitude are used to show a map of the event’s location. For more " "information on finding these values for a given location, " "'<a href='https://support.google.com/maps/answer/18539'>" "see this article</a>" ), ), MultiFieldPanel( [ FieldPanel("venue_name"), FieldPanel("venue_url"), FieldPanel("address_line_1"), FieldPanel("address_line_2"), FieldPanel("address_line_3"), FieldPanel("city"), FieldPanel("state"), FieldPanel("zip_code"), FieldPanel("country"), ], heading="Event address", classname="collapsible", help_text=( "Optional address fields. The city and country are also shown " "on event cards" ), ), MultiFieldPanel( [InlinePanel("topics")], heading="Topics", help_text=( "These are the topic pages the event will appear on. The first topic " "in the list will be treated as the primary topic and will be shown " "in the page’s related content." ), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and description " "for SEO purposes" ), ), ] # Settings panels settings_panels = [FieldPanel("slug")] edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] ) @property def is_upcoming(self): """Returns whether an event is in the future.""" return self.start_date > datetime.date.today() @property def primary_topic(self): """Return the first (primary) topic specified for the event.""" article_topic = self.topics.first() return article_topic.topic if article_topic else None @property def month_group(self): return self.start_date.replace(day=1) @property def country_group(self): return ( {"slug": self.country.code.lower(), "title": self.country.name} if self.country else {"slug": ""} ) @property def event_dates(self): """Return a formatted string of the event start and end dates""" event_dates = self.start_date.strftime("%b %-d") if self.end_date and self.end_date != self.start_date: event_dates += " – " start_month = self.start_date.strftime("%m") if self.end_date.strftime("%m") == start_month: event_dates += self.end_date.strftime("%-d") else: event_dates += self.end_date.strftime("%b %-d") return event_dates @property def event_dates_full(self): """Return a formatted string of the event start and end dates, including the year""" return self.event_dates + self.start_date.strftime(", %Y") def has_speaker(self, person): for speaker in self.speakers: # pylint: disable=not-an-iterable if speaker.block_type == "speaker" and str(speaker.value) == str( person.title ): return True return False
class EventTopic(Orderable): event = ParentalKey("Event", related_name="topics") topic = ForeignKey("topics.Topic", on_delete=CASCADE, related_name="+") panels = [PageChooserPanel("topic")]
class ConversationParticipant(BaseModel): """The join table between Conversation and User.""" user = ForeignKey(settings.AUTH_USER_MODEL) conversation = ForeignKey(Conversation)
class FieldFromModelForeignKeyTest(Model): foo_fk = ForeignKey(Foo, on_delete=CASCADE)
class Bar(Model): foo = ForeignKey(Foo, related_name='bars', on_delete=CASCADE, help_text='bar_help_text')
class BillingDocumentBase(models.Model): objects = BillingDocumentManager.from_queryset(BillingDocumentQuerySet)() class STATES(object): DRAFT = 'draft' ISSUED = 'issued' PAID = 'paid' CANCELED = 'canceled' STATE_CHOICES = Choices( (STATES.DRAFT, _('Draft')), (STATES.ISSUED, _('Issued')), (STATES.PAID, _('Paid')), (STATES.CANCELED, _('Canceled'))) series = models.CharField(max_length=20, blank=True, null=True, db_index=True) number = models.IntegerField(blank=True, null=True, db_index=True) customer = models.ForeignKey('Customer') provider = models.ForeignKey('Provider') archived_customer = JSONField(default=dict, null=True, blank=True) archived_provider = JSONField(default=dict, null=True, blank=True) due_date = models.DateField(null=True, blank=True) issue_date = models.DateField(null=True, blank=True, db_index=True) paid_date = models.DateField(null=True, blank=True) cancel_date = models.DateField(null=True, blank=True) sales_tax_percent = models.DecimalField( max_digits=4, decimal_places=2, validators=[MinValueValidator(0.0)], null=True, blank=True) sales_tax_name = models.CharField(max_length=64, blank=True, null=True) currency = models.CharField(choices=currencies, max_length=4, default='USD', help_text='The currency used for billing.') transaction_currency = models.CharField( choices=currencies, max_length=4, help_text='The currency used when making a transaction.') transaction_xe_rate = models.DecimalField( max_digits=16, decimal_places=4, null=True, blank=True, help_text='Currency exchange rate from document currency to ' 'transaction_currency.') transaction_xe_date = models.DateField( null=True, blank=True, help_text='Date of the transaction exchange rate.') pdf = ForeignKey(PDF, null=True) state = FSMField(choices=STATE_CHOICES, max_length=10, default=STATES.DRAFT, verbose_name="State", help_text='The state the invoice is in.') _last_state = None class Meta: abstract = True unique_together = ('provider', 'series', 'number') ordering = ('-issue_date', 'series', '-number') def __init__(self, *args, **kwargs): super(BillingDocumentBase, self).__init__(*args, **kwargs) self._last_state = self.state def mark_for_generation(self): self.pdf.mark_as_dirty() def _issue(self, issue_date=None, due_date=None): if issue_date: self.issue_date = datetime.strptime(issue_date, '%Y-%m-%d').date() elif not self.issue_date and not issue_date: self.issue_date = timezone.now().date() if not self.transaction_xe_rate: if not self.transaction_xe_date: self.transaction_xe_date = self.issue_date try: xe_rate = CurrencyConverter.convert(1, self.currency, self.transaction_currency, self.transaction_xe_date) except RateNotFound: raise TransitionNotAllowed('Couldn\'t automatically obtain an ' 'exchange rate.') self.transaction_xe_rate = xe_rate if due_date: self.due_date = datetime.strptime(due_date, '%Y-%m-%d').date() elif not self.due_date and not due_date: delta = timedelta(days=PAYMENT_DUE_DAYS) self.due_date = timezone.now().date() + delta if not self.sales_tax_name: self.sales_tax_name = self.customer.sales_tax_name if not self.sales_tax_percent: self.sales_tax_percent = self.customer.sales_tax_percent if not self.number: self.number = self._generate_number() self.archived_customer = self.customer.get_archivable_field_values() @transition(field=state, source=STATES.DRAFT, target=STATES.ISSUED) def issue(self, issue_date=None, due_date=None): self._issue(issue_date=issue_date, due_date=due_date) def _pay(self, paid_date=None): if paid_date: self.paid_date = datetime.strptime(paid_date, '%Y-%m-%d').date() if not self.paid_date and not paid_date: self.paid_date = timezone.now().date() @transition(field=state, source=STATES.ISSUED, target=STATES.PAID) def pay(self, paid_date=None): self._pay(paid_date=paid_date) def _cancel(self, cancel_date=None): if cancel_date: self.cancel_date = datetime.strptime(cancel_date, '%Y-%m-%d').date() if not self.cancel_date and not cancel_date: self.cancel_date = timezone.now().date() @transition(field=state, source=STATES.ISSUED, target=STATES.CANCELED) def cancel(self, cancel_date=None): self._cancel(cancel_date=cancel_date) def sync_related_document_state(self): if self.related_document and self.state != self.related_document.state: state_transition_map = { BillingDocumentBase.STATES.ISSUED: 'issue', BillingDocumentBase.STATES.CANCELED: 'cancel', BillingDocumentBase.STATES.PAID: 'pay' } transition_name = state_transition_map[self.state] bound_transition_method = getattr(self.related_document, transition_name) bound_transition_method() def clone_into_draft(self): copied_fields = { 'customer': self.customer, 'provider': self.provider, 'currency': self.currency, 'sales_tax_percent': self.sales_tax_percent, 'sales_tax_name': self.sales_tax_name } clone = self.__class__._default_manager.create(**copied_fields) clone.state = self.STATES.DRAFT # clone entries too for entry in self._entries: entry_clone = entry.clone() document_type_name = self.__class__.__name__.lower() setattr(entry_clone, document_type_name, clone) entry_clone.save() clone.save() return clone def clean(self): super(BillingDocumentBase, self).clean() # The only change that is allowed if the document is in issued state # is the state chage from issued to paid # !! TODO: If _last_state == 'issued' and self.state == 'paid' || 'canceled' # it should also be checked that the other fields are the same bc. # right now a document can be in issued state and someone could # send a request which contains the state = 'paid' and also send # other changed fields and the request would be accepted bc. only # the state is verified. if self._last_state == self.STATES.ISSUED and\ self.state not in [self.STATES.PAID, self.STATES.CANCELED]: msg = 'You cannot edit the document once it is in issued state.' raise ValidationError({NON_FIELD_ERRORS: msg}) if self._last_state == self.STATES.CANCELED: msg = 'You cannot edit the document once it is in canceled state.' raise ValidationError({NON_FIELD_ERRORS: msg}) # If it's in paid state => don't allow any changes if self._last_state == self.STATES.PAID: msg = 'You cannot edit the document once it is in paid state.' raise ValidationError({NON_FIELD_ERRORS: msg}) if self.transactions.exclude( currency=self.transaction_currency).exists(): message = 'There are unfinished transactions of this document that use a ' \ 'different currency.' raise ValidationError({'transaction_currency': message}) def save(self, *args, **kwargs): if not self.transaction_currency: self.transaction_currency = self.customer.currency or self.currency if not self.series: self.series = self.default_series # Generate the number if not self.number and self.state != BillingDocumentBase.STATES.DRAFT: self.number = self._generate_number() # Add tax info if not self.sales_tax_name: self.sales_tax_name = self.customer.sales_tax_name if not self.sales_tax_percent: self.sales_tax_percent = self.customer.sales_tax_percent self._last_state = self.state with db_transaction.atomic(): # Create pdf object if not self.pdf and self.state != self.STATES.DRAFT: self.pdf = PDF.objects.create( upload_path=self.get_pdf_upload_path(), dirty=1) super(BillingDocumentBase, self).save(*args, **kwargs) def _generate_number(self, default_starting_number=1): """Generates the number for a proforma/invoice.""" default_starting_number = max(default_starting_number, 1) documents = self.__class__._default_manager.filter( provider=self.provider, series=self.series) if not documents.exists(): # An invoice/proforma with this provider and series does not exist if self.series == self.default_series: return self._starting_number else: return default_starting_number else: # An invoice with this provider and series already exists max_existing_number = documents.aggregate( Max('number'))['number__max'] if max_existing_number: if self._starting_number and self.series == self.default_series: return max(max_existing_number + 1, self._starting_number) else: return max_existing_number + 1 else: return default_starting_number def series_number(self): if self.series: if self.number: return "%s-%d" % (self.series, self.number) else: return "%s-draft-id:%d" % (self.series, self.pk) else: return "draft-id:%d" % self.pk series_number.short_description = 'Number' series_number = property(series_number) def __unicode__(self): return u'%s %s => %s [%.2f %s]' % ( self.series_number, self.provider.billing_name, self.customer.billing_name, self.total, self.currency) @property def updateable_fields(self): return [ 'customer', 'provider', 'due_date', 'issue_date', 'paid_date', 'cancel_date', 'sales_tax_percent', 'sales_tax_name', 'currency' ] @property def admin_change_url(self): url_base = 'admin:{app_label}_{klass}_change'.format( app_label=self._meta.app_label, klass=self.__class__.__name__.lower()) url = reverse(url_base, args=(self.pk, )) return '<a href="{url}">{display_series}</a>'.format( url=url, display_series=self.series_number) @property def _entries(self): # entries iterator which replaces the invoice/proforma from the DB with # self. We need this in generate_pdf so that the data in PDF has the # lastest state for the document. Without this we get in template: # # invoice.issue_date != entry.invoice.issue_date # # which is obviously false. document_type_name = self.__class__.__name__ # Invoice or Proforma kwargs = {document_type_name.lower(): self} entries = DocumentEntry.objects.filter(**kwargs) for entry in entries: if document_type_name.lower() == 'invoice': entry.invoice = self if document_type_name.lower() == 'proforma': entry.proforma = self yield (entry) @property def transactions(self): return self.transaction_set.all() def get_template_context(self, state=None): customer = Customer(**self.archived_customer) provider = Provider(**self.archived_provider) if state is None: state = self.state return { 'document': self, 'provider': provider, 'customer': customer, 'entries': self._entries, 'state': state } def get_template(self, state=None): provider_state_template = '{provider}/{kind}_{state}_pdf.html'.format( kind=self.kind, provider=self.provider.slug, state=state).lower() provider_template = '{provider}/{kind}_pdf.html'.format( kind=self.kind, provider=self.provider.slug).lower() generic_state_template = '{kind}_{state}_pdf.html'.format( kind=self.kind, state=state).lower() generic_template = '{kind}_pdf.html'.format(kind=self.kind).lower() _templates = [ provider_state_template, provider_template, generic_state_template, generic_template ] templates = [] for t in _templates: templates.append('billing_documents/' + t) return select_template(templates) def get_pdf_filename(self): return '{doc_type}_{series}-{number}.pdf'.format( doc_type=self.__class__.__name__, series=self.series, number=self.number) def get_pdf_upload_path(self): path_template = getattr( settings, 'SILVER_DOCUMENT_UPLOAD_PATH', 'documents/{provider}/{doc.kind}/{issue_date}/{filename}') context = { 'doc': self, 'filename': self.get_pdf_filename(), 'provider': self.provider.slug, 'customer': self.customer.slug, 'issue_date': self.issue_date.strftime('%Y/%m/%d') } return path_template.format(**context) def generate_pdf(self, state=None, upload=True): # !!! ensure this is not called concurrently for the same document context = self.get_template_context(state) context['filename'] = self.get_pdf_filename() pdf_file_object = self.pdf.generate(template=self.get_template(state), context=context, upload=upload) return pdf_file_object def generate_html(self, state=None, request=None): context = self.get_template_context(state) template = self.get_template(state=context['state']) return template.render(context, request) def serialize_hook(self, hook): """ Used to generate a skinny payload. """ return {'hook': hook.dict(), 'data': {'id': self.id}} @property def entries(self): raise NotImplementedError @property def total(self): return sum([entry.total for entry in self.entries]) @property def total_before_tax(self): return sum([entry.total_before_tax for entry in self.entries]) @property def tax_value(self): return sum([entry.tax_value for entry in self.entries]) @property def total_in_transaction_currency(self): return sum( [entry.total_in_transaction_currency for entry in self.entries]) @property def total_before_tax_in_transaction_currency(self): return sum([ entry.total_before_tax_in_transaction_currency for entry in self.entries ]) @property def tax_value_in_transaction_currency(self): return sum([ entry.tax_value_in_transaction_currency for entry in self.entries ]) @property def amount_paid_in_transaction_currency(self): Transaction = apps.get_model('silver.Transaction') return sum([ transaction.amount for transaction in self.transactions.filter( state=Transaction.States.Settled) ]) @property def amount_pending_in_transaction_currency(self): Transaction = apps.get_model('silver.Transaction') return sum([ transaction.amount for transaction in self.transactions.filter( state=Transaction.States.Pending) ]) @property def amount_to_be_charged_in_transaction_currency(self): Transaction = apps.get_model('silver.Transaction') return self.total_in_transaction_currency - sum([ transaction.amount for transaction in self.transactions.filter(state__in=[ Transaction.States.Initial, Transaction.States.Pending, Transaction.States.Settled ]) ])
class Settlement(TimestampModel): """ Settle transaction is created when the settlement class gain the lock The lock is provided by wait_count, when wait_count count the waiting bill is become 0, this settlement gain the lock. Then it will call the method at the end of this file, to creat all the settle transactions. """ title = CharField(max_length=255) description = CharField(max_length=2048, blank=True) owner = ForeignKey(User, on_delete=models.PROTECT) group = ForeignKey(BillGroups, on_delete=models.PROTECT) # counter of unfinished integer # first save will not trigger s_tr creation # after it calculate how many bills it should wait wait_count = PositiveIntegerField(default=1) @property def state(self): # before the s_tr is set up, all is suspend if self.settletransaction_set.count() == 0: return SUSPEND # maping of the state from transaction s = { PREPARE: PREPARE, APPROVED: PREPARE, REJECTED: SUSPEND, # Not exist CONCENCUS: PREPARE, COMMITED: FINISH, # Not exist FINISH: FINISH, SUSPEND: PREPARE, # Not exist in mapping None: PREPARE } # get random one of tr # and return its mapping tr_first = self.settletransaction_set.first() if tr_first: return s[tr_first.state] return PREPARE @property def gdp(self): return Transaction.get_gdp_by_settlement(self) @property def tr_count(self): return Transaction.get_trs_by_settlement(self).count() def get_balance_by_user(self, user): """ Use settle transaction to trace back the balance to reduce the calculation from server """ query = self.settletransaction_set return query.filter(to_u=user).distinct().aggregate( asum=Coalesce(Sum('amount'), Value(0)) )['asum']\ - query.filter(from_u=user).distinct().aggregate( asum=Coalesce(Sum('amount'), Value(0)) )['asum'] def get_actual_pay_by_user(self, user): return Transaction.get_trs_by_settlement(self)\ .filter(from_u=user).aggregate( asum=Coalesce(Sum('amount'), Value(0)) )['asum'] def try_finish(self): """ When all the s_tr is concencus, finish all the tr, and finish all the bills """ if self.settletransaction_set.exclude( state=CONCENCUS).count() == 0: # there's no more s_tr to wait, end this s_tr and # finish all the bills self.settletransaction_set.filter( state=CONCENCUS).update(state=FINISH) Transaction.objects.filter( state=COMMITED, bill__settlement__id=self.id ).update(state=FINISH) def get_waiting_bill(self): """ return:list<bill> @post: forall bill in return ==> bill.state != COMMITED && bill.state != FINISHED """ return Bill.objects\ .exclude(transaction__state=COMMITED)\ .exclude(transaction__state=FINISH)\ .filter(settlement=self)\ .distinct()
class Equipement(Model): armure = ForeignKey(Armure, on_delete=models.CASCADE, default=None) armes = ForeignKey(Arme, on_delete=models.CASCADE, default=None) bourse = ForeignKey(Bourse, on_delete=models.CASCADE, default=None) objets = ForeignKey(Objet, on_delete=models.CASCADE, default=None) outils = ForeignKey(Outil, on_delete=models.CASCADE, default=None)
class Bill(TimestampModel): # informaqtion about this bill title = CharField(max_length=255) description = CharField(max_length=2048, blank=True) # creator of this bill owner = ForeignKey(User, on_delete=models.PROTECT) group = ForeignKey(BillGroups, on_delete=models.PROTECT) """ Settlement will be attach to a bill Nullable foreign key will require both nullable and blankable https://stackoverflow.com/questions/16589069/foreignkey-does-not-allow-null-values Blankable is for validator, nullable is for database creation """ settlement = ForeignKey( 'Settlement', on_delete=models.SET_NULL, null=True, blank=True ) @property def state(self): # maping of the state from transaction s = { PREPARE: PREPARE, APPROVED: PREPARE, REJECTED: SUSPEND, CONCENCUS: CONCENCUS, COMMITED: COMMITED, FINISH: FINISH, SUSPEND: SUSPEND, None: SUSPEND } # get random one of tr # and return its mapping tr_first = self.transaction_set.first() if tr_first: return s[tr_first.state] return PREPARE def tr_state_update(self, request_uesr, to_state): """ @request_user: who make this request @to_state: which state is this request is make to @pre: tr update is successful @post: if forall tr.state == APPROVE and \ tr.bill.settlement == NULL then all tr.state == CONCENCUS @post: if forall tr.state == APPROVE and \ tr.bill.settlement != NULL then all tr.state == COMMITED @post: if to_state == REJECTED and \ then for all tr.state != REJECTED, tr.state = REJECTED and this.exclude(tr__state = REJECTED).count() == 1 """ # filter out the transaction set might need to update trs = self.transaction_set.all() if to_state == APPROVED: if trs.exclude(state=APPROVED).count() == 0: # decrease the lock of settlement # if there's any if self.settlement != None: # if the settelment is attached, # then the state is directly to COMMITED trs.update(state=COMMITED) assert(trs.count != 0) # then here will infrom the settlement to settup the # settlement transactions self.settlement.wait_count -= 1 self.settlement.save() return # ELSE # all transations are approved # push to next stage for all the transactions trs.update(state=CONCENCUS) else: # need more waiting return elif to_state == REJECTED: # push all the bill to suspend state for tr in trs.exclude(from_u=request_uesr): tr.state = SUSPEND tr.save() @classmethod def approve_all(cls, request_user): # find all the pending bill he need to pay, # then pay them all bills = cls.objects\ .filter( transaction__state=PREPARE, transaction__from_u=request_user ) """ O(n^2) operation, no way to get faster """ for b in bills: b.approve(request_user) @classmethod def filter_user_bills(cls, user): return cls.objects.filter( Q(owner=user) | Q(transaction__from_u=user) | Q(transaction__to_u=user) ).distinct() @classmethod def get_process(cls, user): return cls.filter_user_bills(user).filter( Q(transaction__state=PREPARE) | Q(transaction__state=APPROVED) | Q(transaction__state=SUSPEND) | Q(transaction__state=REJECTED) ).distinct() def approve(self, request_uesr): # the transactions need to approved by request user if self.transaction_set.get(from_u=request_uesr).approve(): self.tr_state_update(request_uesr, APPROVED) def reject(self, request_uesr): myTr = self.transaction_set.get( from_u=request_uesr ) if request_uesr == self.owner: print("force reject now") ret = myTr.force_reject() else: ret = myTr.reject() if ret: self.tr_state_update(request_uesr, REJECTED) def resume(self, request_user): if request_user == self.owner: for tr in self.transaction_set.all(): tr.resume() if tr.from_u == request_user: tr.approve() return True return False # def suspend(self, request_user): def commit(self): """ Non 'public' method, it's called by Settlement class """ if self.state == CONCENCUS: for tr in self.transaction_set.all(): # update all it's transaction to commit tr.commit() return True return False def revoke(self): if self.state == COMMITED: for tr in self.transaction_set.all(): # update all it's transaction to CONCENCUS tr.revoke() return True return False
class TestQuestion(Model): test = ForeignKey("Test", on_delete=CASCADE, related_name="questions") text = TextField()
class LocationCapability(Model): location = ForeignKey(Location, null=True, on_delete=CASCADE) capability = CharField(max_length=50, choices=SQUARE_CAPABILITIES, default="CREDIT_CARD_PROCESSING")
class SQLQuery(models.Model): query = TextField() start_time = DateTimeField(null=True, blank=True, default=timezone.now) end_time = DateTimeField(null=True, blank=True) time_taken = FloatField(blank=True, null=True) request = ForeignKey('Request', related_name='queries', null=True, blank=True, db_index=True) traceback = TextField() objects = SQLQueryManager() # TODO: Document SILKY_PROJECT_ROOT_DIR and SILKY_PROJECT_EXCLUDE_DIRS project_dir = getattr(settings, 'SILKY_PROJECT_ROOT_DIR', None) or '' exclude_dirs = getattr(settings, 'SILKY_PROJECT_EXCLUDE_DIRS', None) or [] if project_dir and project_dir[-1] != os.sep: project_dir += os.sep traceback_pattern = re.compile( r'File "%s(?P<file>.*)", line (?P<line>\d+), in (?P<method>.*)' % re.escape(project_dir)) exclude_patterns = [ re.compile(r'File ".*silk%ssql.*", line \d+, in .*' % os.sep), re.compile(r'File ".*django%sdb%smodels.*", line \d+, in .*' % (os.sep, os.sep)), ] for exclude_dir in exclude_dirs: if exclude_dir[-1] != os.sep: exclude_dir += os.sep exclude_patterns.append( re.compile(r'File "%s.*", line \d+, in .*' % re.escape(exclude_dir))) def match_tb(self, tb): tb = tb.strip() match = self.traceback_pattern.match(tb) for exclude_pattern in self.exclude_patterns: if not match: break if exclude_pattern.match(tb): match = False return match @property def traceback_ln_only(self): return '\n'.join(self.traceback.split('\n')[::2]) @property def traceback_ln_only_with_highlights(self): return [(tb, bool(self.match_tb(tb))) for tb in self.traceback.split('\n')[::2]] @property def formatted_query(self): return sqlparse.format(self.query, reindent=True, keyword_case='upper') # TODO: Surely a better way to handle this? May return false positives @property def num_joins(self): return self.query.lower().count('join ') @property def tables_involved(self): """A really rather rudimentary way to work out tables involved in a query. TODO: Can probably parse the SQL using sqlparse etc and pull out table info that way?""" components = [x.strip() for x in self.query.split()] tables = [] for idx, c in enumerate(components): # TODO: If django uses aliases on column names they will be falsely identified as tables... if c.lower() in ['from', 'join', 'as', 'into', 'update']: try: nxt = components[idx + 1] if nxt.startswith('('): # Subquery continue if nxt.startswith('@'): # Temporary table continue stripped = nxt.strip().strip(',') if stripped: tables.append(stripped) except IndexError: # Reach the end pass return tables @property def last_project_method(self): for tb in self.traceback.split('\n')[::2]: match = self.match_tb(tb) if match: return "%s in %s:%s" % (match.group('method'), match.group('file'), match.group('line')) return "" def calculate_time_taken(self): if self.end_time and self.start_time: interval = self.end_time - self.start_time self.time_taken = interval.total_seconds() * 1000 @transaction.commit_on_success() def save(self, *args, **kwargs): self.calculate_time_taken() super(SQLQuery, self).save(*args, **kwargs) @transaction.commit_on_success() def delete(self, *args, **kwargs): self.request.num_sql_queries -= 1 self.request.save() super(SQLQuery, self).delete(*args, **kwargs)
class Catalog(Model): location = ForeignKey(Location, null=True, on_delete=SET_NULL) name = CharField(max_length=255, blank=True, null=True) version = BigIntegerField(blank=True, null=True) def __str__(self): retn = self.name if self.location is not None: retn = "{} at {}".format(self.name, self.location) return retn def list(self): if self.version is not None: result = square_client.catalog.list_catalog(types="ITEM", version=self.version) else: result = square_client.catalog.list_catalog(types="ITEM") cat = result.body.get( "objects") if result.is_success() else result.errors for item in cat: object_id = item.get("id") item_data = item.get("item_data") self.version = item.get("version") try: catalog_item = CatalogItem.objects.get(object_id=object_id) except CatalogItem.DoesNotExist: catalog_item = CatalogItem.objects.create( catalog=self, name=item_data.get("name"), description=item_data.get("description"), item_abbreviation=item_data.get("abbreviation"), object_id=object_id, ) else: catalog_item.name = item_data.get("name") catalog_item.description = item_data.get("description") catalog_item.item_abbreviation = item_data.get("abbreviation") catalog_item.save() for variation in item_data.get("variations"): v_object_id = variation.get("id") v_item_data = variation.get("item_variation_data") try: variant = CatalogVariant.objects.get(object_id=v_object_id) except CatalogVariant.DoesNotExist: variant = CatalogVariant.objects.create( catalog_item=catalog_item, name=v_item_data.get("name"), price=v_item_data.get("price_money").get("amount"), object_id=v_object_id, ) else: variant.catalog_item = catalog_item variant.name = v_item_data.get("name") variant.price = v_item_data.get("price_money").get( "amount") variant.object_id = v_object_id variant.save() catalog_item.save() self.save() return cat
class Ingredient(Model): recipe = ForeignKey(Recipe, on_delete=CASCADE) food = ForeignKey(Food, on_delete=PROTECT) amount = DecimalField(max_digits=5, decimal_places=2)
class CatalogItem(Model): catalog = ForeignKey(Catalog, blank=True, null=True, on_delete=CASCADE) name = CharField(max_length=255, blank=True, null=True) description = TextField(blank=True, null=True) item_abbreviation = CharField(max_length=255, blank=True, null=True) item_key = UUIDField(blank=True, null=True) item_id = CharField(max_length=255, blank=True, null=True) object_id = CharField(max_length=255, blank=True, null=True) def __str__(self): return self.name def update_id_mappings(self, id_mappings=[]): for id_mapping in id_mappings: object_id = id_mapping.get("client_object_id ") try: variant = CatalogVariant.objects.get(catalog_item=self, variant_key=object_id) except CatalogVariant.DoesNotExist: pass else: variant.object_id = object_id variant.save_id() def delete(self, using=None, keep_parents=False): super().delete(using, keep_parents) result = square_client.catalog.delete_catalog_object( object_id=self.object_id) response = result.body if result.is_success() else result.errors log_message(response, pretty=True) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.item_abbreviation or self.item_abbreviation is None: self.item_abbreviation = generate_abbreviation(self.name) if not self.item_key or self.item_key is None: self.item_key = str(uuid0.generate()) if not self.item_id: self.item_id = "#{}".format(slugify(self.name)) self.object_id = "#{}".format(slugify(self.name)) super().save(force_insert, force_update, using, update_fields) itemid = self.item_id if not self._state.adding: if self.object_id is not None: itemid = self.object_id body = self.as_dict(itemid) log_message(body, pretty=True) if self.variants.count() > 0: result = square_client.catalog.upsert_catalog_object(body=body) catalog_item = result.body if result.is_success( ) else result.errors if "catalog_object" in catalog_item: self.catalog.version = catalog_item.get("catalog_object").get( "version") self.object_id = catalog_item.get("catalog_object").get("id") super().save(force_insert, force_update, using, update_fields) try: id_mappings = catalog_item.get("id_mappings", []) except AttributeError: log_message(catalog_item, pretty=True) else: self.update_id_mappings(id_mappings) @property def idempotency_key(self): return str(uuid0.generate()) @property def variants(self): return CatalogVariant.objects.filter(catalog_item=self) @property def abbreviation(self): pieces = self.name.split() return self.name[0:2] if len(pieces) == 1 else "".join( [x[0] for x in pieces]) def get_item(self): version = self.catalog.version if not version: result = square_client.catalog.retrieve_catalog_object( object_id=self.item_key) else: result = square_client.catalog.retrieve_catalog_object( object_id=self.item_key, catalog_version=version) item = result.body if result.is_success() else result.errors if "object" in item: item = item.get("object") return item def as_dict(self, item_id): variations = [variant.as_dict() for variant in self.variants] return { "idempotency_key": self.idempotency_key, "object": { "type": "ITEM", "id": item_id, "item_data": { "name": self.name, "description": self.description, "abbreviation": self.item_abbreviation, "variations": variations, }, }, }
class Entry(models.Model): TYPE_TUTORIAL = 'tutorial' TYPE_PROJECT = 'project' TYPE_JUPYTER = 'jupyter' TYPE_CHOICES = ( (TYPE_TUTORIAL, 'Tutorial'), (TYPE_PROJECT, 'Project'), (TYPE_JUPYTER, 'Jupyter Notebook') ) ICON_MAP = { TYPE_TUTORIAL: '<i class="fa fa-graduation-cap"></i>', TYPE_PROJECT: '<i class="fa fa-bolt"></i>', TYPE_JUPYTER: '<i class="fa fa-code"></i>', } type = CharField(max_length=10, choices=TYPE_CHOICES) title = CharField(max_length=250, help_text='The main title of the entry. Does not have to be unique') subtitle = CharField(max_length=250, default='', blank=True) description = CharField(max_length=200, default='', blank=True) author = ForeignKey(User, on_delete=models.CASCADE, default=1, related_name='tutorials') thumbnail = FilerImageField(related_name="tutorial_thumbnail", on_delete=models.CASCADE, null=True) slug = SlugField(max_length=250, default='auto', unique=True) content = TextField(default='') text = TextField(default='') publishing_date = DateTimeField(default=timezone.now) creation_date = DateTimeField(default=timezone.now) next = URLField(null=True, blank=True) previous = URLField(null=True, blank=True) comments = GenericRelation(Comment) def save(self, *args, **kwargs) -> None: if self.slug == 'auto': self.slug = slugify(self.title) # So we need the "text" property to literally just contain the content of the post in all lower case and # without all the html markup (this will be used for fulltext search of keywords). BeautifulSoup does this # really well natively. soup = BeautifulSoup(self.content, 'lxml') self.text = soup.text.lower() super(Entry, self).save(*args, **kwargs) @classmethod def get_most_recent(cls, n: int) -> List[Entry]: entries = cls.objects.order_by('-publishing_date').exclude(publishing_date__gte=timezone.now()).all()[0:n] return entries @property def comment_count(self): return len(self.comments.filter(active=True)) @property def fa_icon(self): return self.ICON_MAP[self.type] @property def url(self) -> str: """ Returns the absolute URL of the detail view of the entry instance. :return: """ # This is a bit hacky (because it technically stretches the limits of SOC a bit) but very convenient! # Here is the thing: This is an abstract base model which will be the base for multiple specific # implementations, but we want a common list view for those and a list view would need knowledge of the urls # of the detail pages to link to those correctly. Thus we need a generalized method for getting those aka # we cannot do the {% url "..." %} thing because for that we need to provide a *specific* model type, which we # generally do not know at that point since it could be any specific implementation. Sooo it would be better if # the url information for the detail page would be attached to every individual element right away <- this # property. # Now this only works because of an implicit assumption we make: In the "url.py" module we set the name of # the according detail view for a model to the "type" property of the corresponding model class. If we adhere # to this convention, we can make use of it here and already assemble all the information to let django reverse # the url for a specific Entry instance... view_name = f'{BlogAppConfig.name}:{self.type}' return reverse_lazy(view_name, kwargs={'slug': self.slug}) def __str__(self): return f'{self.type.upper()}: {self.title}'
class CatalogVariant(Model): catalog_item = ForeignKey(CatalogItem, blank=True, null=True, on_delete=CASCADE) name = CharField(max_length=255, blank=True, null=True) item_id = UUIDField(blank=True, null=True) price = PositiveIntegerField(null=True) variant_key = CharField(max_length=255, blank=True, null=True) object_id = CharField(max_length=255, blank=True, null=True) rfid_id = CharField(max_length=255, blank=True, null=True) upc_id = CharField(max_length=255, blank=True, null=True) def __str__(self): return "{} variant of {}".format(self.name, self.catalog_item) def save_id(self, force_insert=False, force_update=False, using=None, update_fields=None): super().save(force_insert, force_update, using, update_fields) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): if not self.item_id: self.item_id = str(uuid0.generate()) if not self.variant_key: self.variant_key = "#{}".format(slugify(self.name)) self.object_id = "#{}".format(slugify(self.name)) super().save(force_insert, force_update, using, update_fields) if self.catalog_item is not None: self.catalog_item.save() def as_dict(self): item_id = None if self.catalog_item: if self.catalog_item.object_id is not None: item_id = self.catalog_item.object_id elif self.catalog_item.item_key: item_id = self.catalog_item.item_id return { "type": "ITEM_VARIATION", "id": self.object_id, "item_variation_data": { "item_id": item_id, "name": self.name, "pricing_type": "FIXED_PRICING", "price_money": { "amount": self.price, "currency": "USD" }, }, }
class AppRelease(TranslatableModel): version = CharField(max_length=256, verbose_name=_('Version'), help_text=_('Version follows Semantic Versioning')) app = ForeignKey('App', on_delete=CASCADE, verbose_name=_('App'), related_name='releases') # dependencies php_extensions = ManyToManyField('PhpExtension', blank=True, through='PhpExtensionDependency', verbose_name=_( 'PHP extension dependency')) databases = ManyToManyField('Database', blank=True, through='DatabaseDependency', verbose_name=_('Database dependency')) licenses = ManyToManyField('License', verbose_name=_('License')) shell_commands = ManyToManyField('ShellCommand', blank=True, verbose_name=_( 'Shell command dependency')) php_version_spec = CharField(max_length=256, verbose_name=_('PHP version requirement')) platform_version_spec = CharField(max_length=256, verbose_name=_( 'Platform version requirement')) raw_php_version_spec = CharField(max_length=256, verbose_name=_( 'PHP version requirement (raw)')) raw_platform_version_spec = CharField(max_length=256, verbose_name=_( 'Platform version requirement (raw)')) min_int_size = IntegerField(blank=True, default=32, verbose_name=_('Minimum Integer bits'), help_text=_('e.g. 32 for 32bit Integers')) download = URLField(max_length=256, blank=True, verbose_name=_('Archive download URL')) created = DateTimeField(auto_now_add=True, editable=False, verbose_name=_('Created at')) last_modified = DateTimeField(auto_now=True, editable=False, db_index=True, verbose_name=_('Updated at')) signature = TextField(verbose_name=_('Signature'), help_text=_( 'A signature using SHA512 and the app\'s certificate')) translations = TranslatedFields( changelog=TextField(verbose_name=_('Changelog'), help_text=_( 'The release changelog. Can contain Markdown'), default='') ) is_nightly = BooleanField(verbose_name=_('Nightly'), default=False) class Meta: verbose_name = _('App release') verbose_name_plural = _('App releases') unique_together = (('app', 'version', 'is_nightly'),) ordering = ['-version'] def can_update(self, user: User) -> bool: return self.app.owner == user or user in self.app.co_maintainers.all() def can_delete(self, user: User) -> bool: return self.can_update(user) def __str__(self) -> str: return '%s %s' % (self.app, self.version) def is_compatible(self, platform_version, inclusive=False): """Checks if a release is compatible with a platform version :param platform_version: the platform version, not required to be semver compatible :param inclusive: if True the check will also return True if an app requires 9.0.1 and the given platform version is 9.0 :return: True if compatible, otherwise false """ min_version = Version(pad_min_version(platform_version)) spec = Spec(self.platform_version_spec) if inclusive: max_version = Version(pad_max_inc_version(platform_version)) return (min_version in spec or max_version in spec) else: return min_version in spec @property def is_unstable(self): return self.is_nightly or '-' in self.version
class Order(Model): location = ForeignKey(Location, null=True, on_delete=CASCADE) order_id = CharField(max_length=255, blank=True, null=True) order_key = UUIDField(blank=True, null=True) @property def idempotency_key(self): return str(uuid0.generate()) def save(self, force_insert=False, force_update=False, using=None, update_fields=None): super().save(force_insert, force_update, using, update_fields) if self._state.adding: result = square_client.orders.create_order(body=self.as_dict()) else: result = square_client.orders.update_order(order_id=self.order_id, body=self.update_dict()) return result.is_success() def as_dict(self): return { "order": { "location_id": self.location.location_id, "reference_id": self.order_id, "line_items": [ item.as_dict() for item in OrderItem.objects.filter(order=self) ], "taxes": [tax.as_dict() for tax in OrderTax.objects.filter(order=self)], "discounts": [ discount.as_dict() for discount in OrderDiscount.objects.filter(order=self) ], }, "idempotency_key": self.idempotency_key, } def update_dict(self): return { "order": { "location_id": self.location.location_id, "line_items": [ item.as_dict() for item in OrderItem.objects.filter(order=self) ], "taxes": [tax.as_dict() for tax in OrderTax.objects.filter(order=self)], "discounts": [ discount.as_dict() for discount in OrderDiscount.objects.filter(order=self) ], "version": 1, }, "fields_to_clear": ["line_items", "taxes", "discounts"], "idempotency_key": self.idempotency_key, }
class IPRange(CleanSave, TimestampedModel): """Represents a range of IP addresses used for a particular purpose in MAAS, such as a DHCP range or a range of reserved addresses.""" objects = IPRangeManager() subnet = ForeignKey('Subnet', editable=True, blank=False, null=False, on_delete=CASCADE) type = CharField(max_length=20, editable=True, choices=IPRANGE_TYPE_CHOICES, null=False, blank=False) start_ip = MAASIPAddressField(null=False, editable=True, blank=False, verbose_name='Start IP') end_ip = MAASIPAddressField(null=False, editable=True, blank=False, verbose_name='End IP') user = ForeignKey(User, default=None, blank=True, null=True, editable=True, on_delete=PROTECT) # In Django 1.8, CharFields with null=True, blank=True had a default # of '' (empty string), whereas with at least 1.11 that is None. # Force the former behaviour, since the documentation is not very clear # on what should happen. comment = CharField(max_length=255, null=True, blank=True, editable=True, default='') def __repr__(self): return ('IPRange(subnet_id=%r, start_ip=%r, end_ip=%r, type=%r, ' 'user_id=%r, comment=%r)') % (self.subnet_id, self.start_ip, self.end_ip, self.type, self.user_id, self.comment) def __contains__(self, item): return item in self.netaddr_iprange def _raise_validation_error(self, message, fields=None): if fields is None: # By default, highlight the start_ip and the end_ip. fields = ['start_ip', 'end_ip'] validation_errors = {} for field in fields: validation_errors[field] = [message] raise ValidationError(validation_errors) def clean(self): super().clean() try: # XXX mpontillo 2015-12-22: I would rather the Django model field # just give me back an IPAddress, but changing it to do this was # had a much larger impact than I expected. start_ip = IPAddress(self.start_ip) end_ip = IPAddress(self.end_ip) except AddrFormatError: # This validation will be called even if the start_ip or end_ip # field is missing. So we need to check them again here, before # proceeding with the validation (and potentially crashing). self._raise_validation_error( "Start IP address and end IP address are both required.") if end_ip.version != start_ip.version: self._raise_validation_error( "Start IP address and end IP address must be in the same " "address family.") if end_ip < start_ip: self._raise_validation_error( "End IP address must not be less than Start IP address.", fields=['end_ip']) if self.subnet_id is not None: cidr = IPNetwork(self.subnet.cidr) if start_ip not in cidr and end_ip not in cidr: self._raise_validation_error( "IP addresses must be within subnet: %s." % cidr) if start_ip not in cidr: self._raise_validation_error( "Start IP address must be within subnet: %s." % cidr, fields=['start_ip']) if end_ip not in cidr: self._raise_validation_error( "End IP address must be within subnet: %s." % cidr, fields=['end_ip']) if cidr.network == start_ip: self._raise_validation_error( "Reserved network address cannot be included in IP range.", fields=['start_ip']) if cidr.version == 4 and cidr.broadcast == end_ip: self._raise_validation_error( "Broadcast address cannot be included in IP range.", fields=['end_ip']) if (start_ip.version == 6 and self.type == IPRANGE_TYPE.DYNAMIC and netaddr.IPRange(start_ip, end_ip).size < 256): self._raise_validation_error( "IPv6 dynamic range must be at least 256 addresses in size.") self.clean_prevent_dupes_and_overlaps() @property def netaddr_iprange(self): return netaddr.IPRange(self.start_ip, self.end_ip) def get_MAASIPRange(self): purpose = self.type # Using '-' instead of '_' is just for consistency. # APIs in previous MAAS releases used '-' in range types. purpose = purpose.replace('_', '-') return make_iprange(self.start_ip, self.end_ip, purpose=purpose) @transactional def clean_prevent_dupes_and_overlaps(self): """Make sure the new or updated range isn't going to cause a conflict. If it will, raise ValidationError. """ # Check against the valid types before going further, since whether # or not the range overlaps anything that could cause an error heavily # depends on its type. valid_types = {choice[0] for choice in IPRANGE_TYPE_CHOICES} # If model is incomplete, save() will fail, so don't bother checking. if (self.subnet_id is None or self.start_ip is None or self.end_ip is None or self.type is None or self.type not in valid_types): return # No dupe checking is required if the object hasn't been materially # modified. if not self._state.has_any_changed(['type', 'start_ip', 'end_ip']): return # Reserved ranges can overlap allocated IPs but not other ranges. # Dynamic ranges cannot overlap anything (no ranges or IPs). if self.type == IPRANGE_TYPE.RESERVED: unused = self.subnet.get_ipranges_available_for_reserved_range( exclude_ip_ranges=[self]) else: unused = self.subnet.get_ipranges_available_for_dynamic_range( exclude_ip_ranges=[self]) if len(unused) == 0: self._raise_validation_error( "There is no room for any %s ranges on this subnet." % (self.type)) message = "Requested %s range conflicts with an existing " % self.type if self.type == IPRANGE_TYPE.RESERVED: message += "range." else: message += "IP address or range." # Find unused range for start_ip for range in unused: if IPAddress(self.start_ip) in range: if IPAddress(self.end_ip) in range: # Success, start and end IP are in an unused range. return else: self._raise_validation_error(message) self._raise_validation_error(message)
class TBar(Model): foo = ForeignKey(TFoo, on_delete=CASCADE) c = BooleanField() class Meta: ordering = ('pk',)