def ready(self): # Connections may already exist before we are called. for conn in connections.all(): if conn.connection is not None: register_type_handlers(conn) connection_created.connect(register_type_handlers) CharField.register_lookup(Unaccent) TextField.register_lookup(Unaccent) CharField.register_lookup(SearchLookup) TextField.register_lookup(SearchLookup) CharField.register_lookup(TrigramSimilar) TextField.register_lookup(TrigramSimilar)
def __init__(self, *args, **kwargs): """ Allow specifying a different format for the key used to identify versionized content in the model-definition. """ #TODO: check if the string has the correct format self.rcskey_format = kwargs.pop('rcskey_format', "%s/%s/%s/%s.txt") self.IS_VERSIONED = True # so we can figure out that this field is versionized TextField.__init__(self, *args, **kwargs)
def __init__(self, *args, **kwargs): """ Allow specifying a different format for the key used to identify versionized content in the model-definition. """ if kwargs.get("rcskey_format", False): self.rcskey_format = kwargs["rcskey_format"] del kwargs["rcskey_format"] # TODO: check if the string has the correct format else: self.rcskey_format = "%s/%s/%s/%s.txt" self.IS_VERSIONED = True # so we can figure out that this field is versionized TextField.__init__(self, *args, **kwargs)
def ready(self): connection_created.connect(register_hstore_handler) CharField.register_lookup(Unaccent) TextField.register_lookup(Unaccent) CharField.register_lookup(SearchLookup) TextField.register_lookup(SearchLookup) CharField.register_lookup(TrigramSimilar) TextField.register_lookup(TrigramSimilar)
def add_lookups(self): from django.db.models import CharField, TextField from django_mysql.models.lookups import ( CaseSensitiveExact, Soundex, SoundsLike ) CharField.register_lookup(CaseSensitiveExact) CharField.register_lookup(SoundsLike) CharField.register_lookup(Soundex) TextField.register_lookup(CaseSensitiveExact) TextField.register_lookup(SoundsLike) TextField.register_lookup(Soundex)
def uninstall_if_needed(setting, value, enter, **kwargs): """ Undo the effects of PostgresConfig.ready() when django.contrib.postgres is "uninstalled" by override_settings(). """ if not enter and setting == 'INSTALLED_APPS' and 'django.contrib.postgres' not in set(value): connection_created.disconnect(register_type_handlers) CharField._unregister_lookup(Unaccent) TextField._unregister_lookup(Unaccent) CharField._unregister_lookup(SearchLookup) TextField._unregister_lookup(SearchLookup) CharField._unregister_lookup(TrigramSimilar) TextField._unregister_lookup(TrigramSimilar) # Disconnect this receiver until the next time this app is installed # and ready() connects it again to prevent unnecessary processing on # each setting change. setting_changed.disconnect(uninstall_if_needed)
def ready(self): # Connections may already exist before we are called. for conn in connections.all(): if conn.vendor == 'postgresql': conn.introspection.data_types_reverse.update({ 3802: 'django.contrib.postgres.fields.JSONField', 3904: 'django.contrib.postgres.fields.IntegerRangeField', 3906: 'django.contrib.postgres.fields.DecimalRangeField', 3910: 'django.contrib.postgres.fields.DateTimeRangeField', 3912: 'django.contrib.postgres.fields.DateRangeField', 3926: 'django.contrib.postgres.fields.BigIntegerRangeField', }) if conn.connection is not None: register_type_handlers(conn) connection_created.connect(register_type_handlers) CharField.register_lookup(Unaccent) TextField.register_lookup(Unaccent) CharField.register_lookup(SearchLookup) TextField.register_lookup(SearchLookup) CharField.register_lookup(TrigramSimilar) TextField.register_lookup(TrigramSimilar)
def ready(self): connection_created.connect(register_hstore_handler) CharField.register_lookup(Unaccent) TextField.register_lookup(Unaccent)
#************************************************ from django.db.models import Transform, Field class UpperCase(Transform): lookup_name = 'upper' bilateral = True def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) return "UPPER(%s)" % lhs, params from django.db.models import CharField, TextField CharField.register_lookup(UpperCase) TextField.register_lookup(UpperCase) class AbsoluteValue(Transform): lookup_name = 'abs' def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) return "ABS(%s)" % lhs, params @property def output_field(self): return FloatField() #*****************************8
class Location(AbstractStreetAddress): location_id = CharField(max_length=255, blank=True, null=True) location_status = CharField(max_length=10, choices=SQUARE_STATUS, default=STATUS_ACTIVE) location_type = CharField(max_length=10, choices=SQUARE_LOCATION_TYPE, default="PHYSICAL") business_name = CharField(max_length=255, blank=True, null=True) description = TextField(blank=True, null=True) phone_number = CharField(max_length=32, blank=True, null=True) email = EmailField(blank=True, null=True) website_url = URLField(blank=True, null=True) facebook_url = URLField(blank=True, null=True) instagram_username = CharField(max_length=255, blank=True, null=True) twitter_username = CharField(max_length=255, blank=True, null=True) country = CharField(max_length=2, blank=True, null=True) created_at = DateTimeField(null=True) currency = CharField(max_length=10, blank=True, null=True) language_code = CharField(max_length=10, blank=True, null=True) mcc = CharField(max_length=10, blank=True, null=True) merchant_id = CharField(max_length=32, blank=True, null=True) def __str__(self): return self.business_name if self.business_name is not None else self.name @property def mcc_description(self): return iso18245.get_mcc(self.mcc) @property def hours(self): return [ x.as_dict() for x in LocationHours.objects.filter(location=self) ] @property def capabilities(self): return [ x.capability for x in LocationCapability.objects.filter(location=self) ] def as_dict(self): if not self.postal_code: self.link_postal_code() return { "name": self.name, "business_name": self.business_name if self.business_name else self.name, "capabilities": self.capabilities, "coordinates": { "latitude": self.latitude, "longitude": self.longitude }, "address": { "address_line_1": self.address1, "address_line_2": self.address2, "locality": self.city, "administrative_district_level_1": self.postal_code.admin_code1, "administrative_district_level_2": self.postal_code.admin_name2, "postal_code": self.zip_code, "country": self.postal_code.country_code, }, "business_hours": { "periods": [self.hours] }, "description": self.description, "business_email": self.email, "website_url": self.website_url, "facebook_url": self.facebook_url, } def save(self, force_insert=False, force_update=False, using=None, update_fields=None): super(Location, self).save(force_insert, force_update, using, update_fields) location_body = {"location": self.as_dict()} if self._state.adding: result = square_client.locations.create_location( body=location_body) if "location" in result: self.location_id = result.get("location").get("id") super(Location, self).save(force_insert, force_update, using, update_fields) else: result = square_client.locations.update_location( location_id=self.location_id, body=location_body) return result.is_success() def download(self): result = square_client.locations.retrieve_location( location_id=self.location_id) if result.is_success(): location = result.get("location") self.name = (location.get("name"), ) self.business_name = (location.get("business_name"), ) self.country = (location.get("country"), ) self.created_at = (parse(location.get("created_at")), ) self.currency = (location.get("currency"), ) self.language_code = (location.get("language_code"), ) self.mcc = (location.get("mcc"), ) self.merchant_id = (location.get("merchant_id"), ) self.location_status = (location.get("status"), ) self.location_type = (location.get("type"), ) self.address1 = (location.get("address").get("address_line_1"), ) self.address2 = (location.get("address").get("address_line_2"), ) self.city = (location.get("address").get("locality"), ) self.zip_code = (location.get("address").get("postal_code"), ) capabilities = location.get("capabilities") for capability in capabilities: try: LocationCapability.objects.get(location=self, capability=capability) except LocationCapability.DoesNotExist: LocationCapability.objects.create(location=self, capability=capability) LocationCapability.objects.filter(location=self).exclude( capability__in=capabilities).delete() return result.is_success()
class Answer(Model): question = ForeignKey("TestQuestion", on_delete=CASCADE, related_name="answers") text = TextField() is_correct = BooleanField(default=False)
class Townie(User): """Both an almost normal Django User as well as an abstraction over a system user.""" class Meta: verbose_name = 'Townie' verbose_name_plural = 'Townies' shell = CharField(max_length=50, default="/bin/bash") reviewed = BooleanField(default=False) reasons = TextField(blank=True, null=False, default='') displayname = CharField(max_length=100, blank=False, null=False) def send_welcome_email(self, admin_name='vilmibm'): welcome_tmpl = get_template('users/welcome_email.txt') context = { 'username': self.username, 'admin_name': admin_name, } text = welcome_tmpl.render(context) from_address = '{}@tilde.town'.format(admin_name) success = send_email(self.email, text, subject='tilde.town!', frum=from_address) if not success: Ticket.objects.create( name='system', email='*****@*****.**', issue_type='other', issue_text='was not able to send welcome email to {} ({})'. format(self.username, self.email)) # managing concrete system state def create_on_disk(self): """A VERY NOT IDEMPOTENT create function. Originally, I had ambitions to have this be idempotent and able to incrementally update a user as needed, but decided that was overkill for now.""" assert (self.reviewed) dot_ssh_path = '/home/{}/.ssh'.format(self.username) _guarded_run([ 'sudo', 'adduser', '--quiet', '--shell={}'.format(self.shell), '--gecos="{}"'.format(self.displayname), '--disabled-password', self.username, ]) # Create .ssh _guarded_run( ['sudo', '--user={}'.format(self.username), 'mkdir', dot_ssh_path]) # Write out authorized_keys file # Why is this a call out to a python script? There's no secure way with # sudoers to allow this code to write to a file; if this code was to be # compromised, the ability to write arbitrary files with sudo is a TKO. # By putting the ssh key file creation into its own script, we can just # give sudo access for that one command to this code. # # We could put the other stuff from here into that script and then only # grant sudo for the script, but then we're moving code out of this # virtual-env contained, maintainable thing into a script. it's my # preference to have the script be as minimal as possible. with TemporaryFile(dir="/tmp") as fp: fp.write(self.generate_authorized_keys().encode('utf-8')) fp.seek(0) _guarded_run( [ 'sudo', '--user={}'.format(self.username), '/opt/bin/create_keyfile.py', self.username ], stdin=fp, ) def generate_authorized_keys(self): """returns a string suitable for writing out to an authorized_keys file""" content = KEYFILE_HEADER pubkeys = Pubkey.objects.filter(townie=self) for key in pubkeys: if key.key.startswith('ssh-'): content += '\n{}'.format(key.key) else: content += '\n{} {}'.format(key.key_type, key.key) return content
class Comment(Model): username = CharField(max_length=20) message = TextField(max_length=1000) order = TextField(max_length=100) created = DateTimeField(auto_now_add=True) @staticmethod def make(request: HttpRequest, form: CommentForm): message = form.cleaned_data['message'] username = request.user.get_username() or 'anonymous' order = get_next_order(request.POST['order']) new_comment = Comment(username=username, message=message, order=order) new_comment.save() created = new_comment.created return dict(created=created.strftime("%d %B %Y г. %H:%M").lstrip('0'), username=username, message=message, order=order, next=Comment.get_response_order(order)) @staticmethod def last_order(): try: last = Comment.objects.all().order_by('-order')[0].order return last.split('_')[0] except IndexError: return '00000' @staticmethod def comment_tuples() -> List[namedtuple]: return [ dict2namedtuple(dct) for dct in Comment.objects.all().order_by('order').values() ] @staticmethod def get_last_in_dialog(dialog): try: return Comment.objects.filter(order__startswith=dialog).order_by( 'order')[1].order[:len(dialog) + 6] except IndexError: return None @staticmethod def get_response_order(dialog): last = Comment.get_last_in_dialog(dialog) last = get_next_order(last) if last else last return last or dialog + '_00000' @staticmethod def next_tuples(): return [ Comment.get_response_order(dct['order']) for dct in Comment.objects.all().order_by('order').values() ] @staticmethod def get_new_created(last_update): result = [] for i in Comment.objects.filter(created__gt=last_update).values(): dct = dict(i) dct.update({'next': get_next_order(i['order'])}) result.append(dct) return result
class Agreement(BaseModel): group = models.ForeignKey(Group, on_delete=models.CASCADE) title = TextField() content = TextField() users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='agreements', through='UserAgreement')
class Event(CleanSave, TimestampedModel): """An `Event` represents a MAAS event. :ivar type: The event's type. :ivar node: The node of the event. :ivar node_system_id: The system_id of the node of the event. :ivar node_hostname: The hostname of the node of the event. :ivar user_id: The user's id responsible for this event. :ivar username: The username of the user responsible for this event. :ivar ip_address: IP address used in the request for this event. :ivar endpoint: Endpoint used in the request for this event. :ivar user_agent: User agent used in the request for this event. :ivar action: The action of the event. :ivar description: A free-form description of the event. """ type = ForeignKey("EventType", null=False, editable=False, on_delete=PROTECT) # This gets set to None if the node gets deleted from the pre_delete signal node = ForeignKey("Node", null=True, editable=False, on_delete=DO_NOTHING) node_system_id = CharField(max_length=41, blank=True, null=True, editable=False) # Set on node deletion. node_hostname = CharField(max_length=255, default="", blank=True, validators=[validate_hostname]) user_id = IntegerField(blank=True, null=True, editable=False) username = CharField(max_length=150, blank=True, default="") # IP address of the request that caused this event. ip_address = GenericIPAddressField(unique=False, null=True, editable=False, blank=True, default=None) # Endpoint of request used to register the event. endpoint = IntegerField(choices=ENDPOINT_CHOICES, editable=False, default=ENDPOINT.API) # User agent of request used to register the event. user_agent = TextField(default="", blank=True, editable=False) action = TextField(default="", blank=True, editable=False) description = TextField(default="", blank=True, editable=False) objects = EventManager() class Meta(DefaultMeta): verbose_name = "Event record" index_together = (("node", "id"), ) indexes = [ # Needed to get the latest event for each node on the # machine listing page. Index(fields=["node", "-created", "-id"]) ] @property def endpoint_name(self): return ENDPOINT_CHOICES[self.endpoint][1] @property def owner(self): if self.username: return self.username else: return "unknown" @property def hostname(self): if self.node_hostname: return self.node_hostname elif self.node is not None: return self.node.hostname else: return "unknown" @property def render_audit_description(self): return self.description % {"username": self.owner} def __str__(self): return "%s (node=%s, type=%s, created=%s)" % ( self.id, self.node, self.type.name, self.created, ) def validate_unique(self, exclude=None): """Override validate unique so nothing is validated. Since `Event` is never checked for user validaton let Postgres handle the foreign keys instead of Django pre-checking before save. """ pass
class Video(BasePage): resource_type = "video" parent_page_types = ["Videos"] subpage_types = [] template = "video.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, ) body = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES, help_text=( "Optional body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets"), ) related_links_mdn = StreamField( StreamBlock([("link", ExternalLinkBlock())], required=False), null=True, blank=True, help_text="Optional links to MDN Web Docs for further reading", verbose_name="Related MDN links", ) image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) types = CharField(max_length=14, choices=VIDEO_TYPE, default="conference") duration = CharField( max_length=30, blank=True, null=True, help_text= ("Optional video duration in MM:SS format e.g. “12:34”. Shown when the " "video is displayed as a card"), ) transcript = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES, help_text="Optional text transcript of the video, supports rich text", ) video_url = StreamField( StreamBlock([("embed", EmbedBlock())], min_num=1, max_num=1, required=True), help_text= ("Embed URL for the video e.g. https://www.youtube.com/watch?v=kmk43_2dtn0" ), ) speakers = StreamField( StreamBlock( [("speaker", PageChooserBlock(target_model="people.Person"))], required=False, ), blank=True, null=True, help_text= "Optional list of people associated with or starring in the video", ) # 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 date = DateField("Upload date", default=datetime.date.today) keywords = ClusterTaggableManager(through=VideoTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), ImageChooserPanel("image"), StreamFieldPanel("video_url"), FieldPanel("body"), StreamFieldPanel("related_links_mdn"), FieldPanel("transcript"), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ FieldPanel("date"), StreamFieldPanel("speakers"), MultiFieldPanel( [InlinePanel("topics")], heading="Topics", help_text= ("These are the topic pages the video 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."), ), FieldPanel("duration"), MultiFieldPanel( [FieldPanel("types")], heading="Type", help_text="Choose a video type to help people search for the video", ), 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 = [FieldPanel("slug")] # Tabs 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"), ]) def get_absolute_url(self): # For the RSS feed return self.full_url @property def primary_topic(self): """Return the first (primary) topic specified for the video.""" video_topic = self.topics.first() return video_topic.topic if video_topic else None @property def read_time(self): return str(readtime.of_html(str(self.body))) @property def related_resources(self): """Returns resources that are related to the current resource, i.e. live, public articles and videos which have the same topics.""" topic_pks = [topic.topic.pk for topic in self.topics.all()] return get_combined_articles_and_videos( self, topics__topic__pk__in=topic_pks) def has_speaker(self, person): for speaker in self.speakers: # pylint: disable=not-an-iterable if str(speaker.value) == str(person.title): return True return False
class VLAN(CleanSave, TimestampedModel): """A `VLAN`. :ivar name: The short-human-identifiable name for this VLAN. :ivar vid: The VLAN ID of this VLAN. :ivar fabric: The `Fabric` this VLAN belongs to. """ objects = VLANManager() class Meta(DefaultMeta): """Needed for South to recognize this model.""" verbose_name = "VLAN" verbose_name_plural = "VLANs" unique_together = ( ('vid', 'fabric'), ) name = CharField( max_length=256, editable=True, null=True, blank=True, validators=[VLAN_NAME_VALIDATOR]) description = TextField(null=False, blank=True) vid = IntegerField(editable=True) fabric = ForeignKey( 'Fabric', blank=False, editable=True, on_delete=CASCADE) mtu = IntegerField(default=DEFAULT_MTU) dhcp_on = BooleanField(default=False, editable=True) external_dhcp = MAASIPAddressField( null=True, editable=False, blank=True, default=None) primary_rack = ForeignKey( 'RackController', null=True, blank=True, editable=True, related_name='+', on_delete=CASCADE) secondary_rack = ForeignKey( 'RackController', null=True, blank=True, editable=True, related_name='+', on_delete=CASCADE) relay_vlan = ForeignKey( 'self', null=True, blank=True, editable=True, related_name='relay_vlans', on_delete=deletion.SET_NULL) space = ForeignKey( 'Space', editable=True, blank=True, null=True, on_delete=SET_NULL) def __str__(self): return "%s.%s" % (self.fabric.get_name(), self.get_name()) def clean_vid(self): if self.vid is None or self.vid < 0 or self.vid > 4094: raise ValidationError( {'vid': ["VID must be between 0 and 4094."]}) def clean_mtu(self): # Linux doesn't allow lower than 552 for the MTU. if self.mtu < 552 or self.mtu > 65535: raise ValidationError( {'mtu': ["MTU must be between 552 and 65535."]}) def clean(self): self.clean_vid() self.clean_mtu() def is_fabric_default(self): """Is this the default VLAN in the fabric?""" return self.fabric.get_default_vlan() == self def get_name(self): """Return the name of the VLAN.""" if self.is_fabric_default(): return "untagged" elif self.name is not None: return self.name else: return str(self.vid) def manage_connected_interfaces(self): """Deal with connected interfaces: - delete all VLAN interfaces. - reconnect the other interfaces to the default VLAN of the fabric. """ for interface in self.interface_set.all(): if isinstance(interface, VLANInterface): interface.delete() else: interface.vlan = self.fabric.get_default_vlan() interface.save() def manage_connected_subnets(self): """Reconnect subnets the default VLAN of the fabric.""" for subnet in self.subnet_set.all(): subnet.vlan = self.fabric.get_default_vlan() subnet.save() def delete(self): if self.is_fabric_default(): raise ValidationError( "This VLAN is the default VLAN in the fabric, " "it cannot be deleted.") self.manage_connected_interfaces() self.manage_connected_subnets() super(VLAN, self).delete() def save(self, *args, **kwargs): # Bug 1555759: Raise a Notification if there are no VLANs with DHCP # enabled. Clear it when one gets enabled. notifications = Notification.objects.filter( ident="dhcp_disabled_all_vlans") if self.dhcp_on: # No longer true. Delete the notification. notifications.delete() elif (not notifications.exists() and not VLAN.objects.filter(dhcp_on=True).exists()): Notification.objects.create_warning_for_admins( "DHCP is not enabled on any VLAN. This will prevent " "machines from being able to PXE boot, unless an external " "DHCP server is being used.", ident="dhcp_disabled_all_vlans") super().save(*args, **kwargs)
class Blind(Product): name = TextField() child_safe = BooleanField(default=False) def __str__(self): return self.name
class HomePage(Page): subpage_types = [ 'articles.Articles', 'content.ContentPage', 'events.Events', 'people.People', 'topics.Topics', 'videos.Videos', ] template = 'home.html' # Content fields subtitle = TextField(max_length=250, blank=True, default='') button_text = CharField(max_length=30, blank=True, default='') button_url = CharField(max_length=2048, blank=True, default='') image = ForeignKey('mozimages.MozImage', null=True, blank=True, on_delete=SET_NULL, related_name='+') external_promos = StreamField( StreamBlock([ ('external_promo', FeaturedExternalBlock()), ], max_num=2, required=False), null=True, blank=True, help_text= 'Optional promo space under the header for linking to external sites, max. 2', ) featured = StreamField( StreamBlock([ ('article', PageChooserBlock(target_model=( 'articles.Article', 'externalcontent.ExternalArticle', ))), ('external_page', FeaturedExternalBlock()), ], max_num=4, required=False), null=True, blank=True, help_text='Optional space for featured articles, max. 4', ) about_title = TextField(max_length=250, blank=True, default='') about_subtitle = TextField(max_length=250, blank=True, default='') about_button_text = CharField(max_length=30, blank=True, default='') about_button_url = URLField(max_length=140, blank=True, default='') # 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 keywords = ClusterTaggableManager(through=HomePageTag, blank=True) # Editor panel configuration content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('subtitle'), FieldPanel('button_text'), FieldPanel('button_url'), ], heading='Header section', help_text='Optional fields for the header section'), MultiFieldPanel( [ ImageChooserPanel('image'), ], heading='Image', help_text= 'Optional image shown when sharing this page through social media' ), StreamFieldPanel('external_promos'), StreamFieldPanel('featured'), MultiFieldPanel( [ FieldPanel('about_title'), FieldPanel('about_subtitle'), FieldPanel('about_button_text'), FieldPanel('about_button_url'), ], heading='About section', help_text='Optional section to explain more about Mozilla'), ] # Card panels card_panels = [ MultiFieldPanel( [ FieldPanel('card_title'), FieldPanel('card_description'), ImageChooserPanel('card_image'), ], heading='Card overrides', help_text= ('Optional fields to override the default title, description and image when this page is shown as a card' )), ] # Meta panels meta_panels = [ MultiFieldPanel( [ FieldPanel('seo_title'), FieldPanel('search_description'), FieldPanel('keywords'), ], heading='SEO', help_text= 'Optional fields to override the default title and description for SEO purposes' ), ] # Settings panels settings_panels = [ FieldPanel('slug'), ] # Tabs 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'), ]) @classmethod def can_create_at(cls, parent): # Allow only one instance of this page type return super().can_create_at(parent) and not cls.objects.exists() @property def primary_topics(self): """The site’s top-level topics, i.e. topics without a parent topic.""" from ..topics.models import Topic return Topic.objects.filter( parent_topics__isnull=True).live().public().order_by('title')
class KeyTextTransform(KeyTransform): operator = '->>' nested_operator = '#>>' output_field = TextField()
class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) history = TextField() def __str__(self): return f'{self.user.username} Profile'
class Group(BaseModel, LocationModel, ConversationMixin, DirtyFieldsMixin): objects = GroupQuerySet.as_manager() name = models.CharField(max_length=settings.NAME_MAX_LENGTH, unique=True) description = models.TextField(blank=True) welcome_message = models.TextField(blank=True) members = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name='groups', through='GroupMembership', through_fields=('group', 'user'), ) public_description = models.TextField(blank=True) application_questions = models.TextField(blank=True) status = models.CharField( default=GroupStatus.ACTIVE.value, choices=[(status.value, status.value) for status in GroupStatus], max_length=100, ) theme = models.TextField( default=GroupTheme.FOODSAVING.value, choices=[(theme.value, theme.value) for theme in GroupTheme], ) sent_summary_up_to = DateTimeField(null=True) timezone = TimeZoneField(default='Europe/Berlin', null=True, blank=True) active_agreement = models.OneToOneField( 'groups.Agreement', related_name='active_group', null=True, on_delete=models.SET_NULL, ) last_active_at = DateTimeField(default=tz.now) is_open = models.BooleanField(default=False) photo = VersatileImageField( 'Group Photo', upload_to='group_photos', null=True, ) features = ArrayField(TextField(), default=list) @property def group(self): return self @property def conversation_supports_threads(self): return True def __str__(self): return 'Group {}'.format(self.name) def add_member(self, user, added_by=None, history_payload=None): membership = GroupMembership.objects.create( group=self, user=user, added_by=added_by, ) History.objects.create( typus=HistoryTypus.GROUP_JOIN, group=self, users=[user], payload=history_payload, ) return membership def remove_member(self, user): History.objects.create(typus=HistoryTypus.GROUP_LEAVE, group=self, users=[user]) GroupMembership.objects.filter(group=self, user=user).delete() def is_member(self, user): return not user.is_anonymous and GroupMembership.objects.filter(group=self, user=user).exists() def is_editor(self, user): return self.is_member_with_role(user, roles.GROUP_EDITOR) def is_member_with_role(self, user, role_name): return not user.is_anonymous and GroupMembership.objects.filter( group=self, user=user, roles__contains=[role_name] ).exists() def is_playground(self): return self.status == GroupStatus.PLAYGROUND.value def accept_invite(self, user, invited_by, invited_at): self.add_member( user, added_by=invited_by, history_payload={ 'invited_by': invited_by.id, 'invited_at': invited_at.isoformat(), 'invited_via': 'e-mail' } ) def refresh_active_status(self): self.last_active_at = tz.now() if self.status == GroupStatus.INACTIVE.value: self.status = GroupStatus.ACTIVE.value self.save() def has_recent_activity(self): return self.last_active_at >= tz.now() - timedelta(days=settings.NUMBER_OF_DAYS_UNTIL_GROUP_INACTIVE) def get_application_questions_or_default(self): return self.application_questions or self.application_questions_default() def application_questions_default(self): return render_to_string('default_application_questions.nopreview.jinja2') def trust_threshold_for_newcomer(self): count = getattr(self, '_yesterdays_member_count', None) if count is None: one_day_ago = timezone.now() - relativedelta(days=1) count = self.groupmembership_set.active().filter(created_at__lte=one_day_ago).count() dynamic_threshold = max(1, count // 2) trust_threshold = min(settings.GROUP_EDITOR_TRUST_MAX_THRESHOLD, dynamic_threshold) return trust_threshold def active_editors_count(self): count = getattr(self, '_active_editors_count', None) if count is None: count = self.groupmembership_set.active().editors().count() return count def delete_photo(self): if self.photo.name is None: return # Deletes Image Renditions self.photo.delete_all_created_images() # Deletes Original Image self.photo.delete(save=False) def welcome_message_rendered(self, **kwargs): return markdown.render(self.welcome_message, **kwargs)
class Publishable(Model): """ Base model for Article and Page models. """ preview_id = UUIDField(default=uuid.uuid4) revision_id = PositiveIntegerField(default=0, db_index=True) head = NullBooleanField(default=None, db_index=True, null=True) is_published = NullBooleanField(default=None, db_index=True) is_active = NullBooleanField(default=True) published_version = PositiveIntegerField(null=True) latest_version = PositiveIntegerField(null=True) slug = SlugField(max_length=255, db_index=True) shares = PositiveIntegerField(default=0, blank=True, null=True) views = PositiveIntegerField(default=0) featured_image = ForeignKey('ImageAttachment', on_delete=SET_NULL, related_name='%(class)s_featured_image', blank=True, null=True) featured_video = ForeignKey('VideoAttachment', on_delete=SET_NULL, related_name='%(class)s_featured_video', blank=True, null=True) template = CharField(max_length=255, default='default') template_data = JSONField(default={}) seo_keyword = CharField(max_length=100, null=True) seo_description = TextField(null=True) integrations = JSONField(default={}) content = JSONField(default=[]) snippet = TextField(null=True) created_at = DateTimeField() updated_at = DateTimeField() published_at = DateTimeField(null=True) objects = PublishableManager() @property def template_fields(self): if not hasattr(self, '_template_fields'): template = self.get_template() if template: template.set_data(self.template_data) self._template_fields = template.prepare_data() return self._template_fields def add_view(self): self.views += 1 self.save(revision=False) def get_template_path(self): if self.template != 'default': return 'article/%s.html' % self.template else: return 'article/default.html' def get_template(self): if not hasattr(self, '_template'): from dispatch.theme import ThemeManager try: self._template = ThemeManager.Templates.get(self.template) except: self._template = None return self._template @property def html(self): """Return HTML representation of content""" return content_to_html(self.content, self.id) def is_parent(self): return self.parent is None def publish(self): # Unpublish last published version type(self).objects.filter(parent=self.parent, is_published=True).update(is_published=None, published_at=None) self.is_published = True if self.published_at is None: self.published_at = timezone.now() self.save(revision=False) # Set published version for all articles type(self).objects.filter(parent=self.parent).update( published_version=self.revision_id) self.published_version = self.revision_id return self def unpublish(self): type(self).objects.filter(parent=self.parent, is_published=True).update(is_published=None, published_at=None) self.is_published = None # Unset published version for all articles type(self).objects.filter(parent=self.parent).update( published_version=None) self.published_version = None return self # Overriding @transaction.atomic def save(self, revision=True, *args, **kwargs): """ Handles the saving/updating of a Publishable instance. Arguments: revision - if True, a new version of this Publishable will be created. """ if revision: # If this is a revision, set it to be the head of the list and increment the revision id self.head = True self.revision_id += 1 previous_revision = self.get_previous_revision() if not self.is_parent(): # If this is a revision, delete the old head of the list. type(self).objects \ .filter(parent=self.parent, head=True) \ .update(head=None) # Clear the instance id to force Django to save a new instance. # Both fields (pk, id) required for this to work -- something to do with model inheritance self.pk = None self.id = None # New version is unpublished by default self.is_published = None # Set created_at to current time, but only for first version if not self.created_at: self.created_at = timezone.now() self.updated_at = timezone.now() if revision: self.updated_at = timezone.now() super(Publishable, self).save(*args, **kwargs) # Update the parent foreign key if not self.parent: self.parent = self super(Publishable, self).save(update_fields=['parent']) if revision: # Set latest version for all articles type(self).objects \ .filter(parent=self.parent) \ .update(latest_version=self.revision_id) self.latest_version = self.revision_id return self # Overriding delete the parent article to cascade delete all versions def delete(self, *args, **kwargs): if self.parent == self: return super(Publishable, self).delete(*args, **kwargs) return self.parent.delete() def save_featured_image(self, data): """ Handles saving the featured image. If data is None, the featured image will be removed. `data` should be dictionary with the following format: { 'image_id': int, 'caption': str, 'credit': str } """ attachment = self.featured_image if data is None: if attachment: attachment.delete() self.featured_image = None return if data['image_id'] is None: if attachment: attachment.delete() self.featured_image = None return if not attachment: attachment = ImageAttachment() attachment.image_id = data.get('image_id', attachment.image_id) attachment.caption = data.get('caption', None) attachment.credit = data.get('credit', None) instance_type = str(type(self)).lower() setattr(attachment, instance_type, self) attachment.save() self.featured_image = attachment def save_featured_video(self, data): attachment = self.featured_video if data is None: if attachment: attachment.delete() self.featured_video = None return if data['video_id'] is None: if attachment: attachment.delete() self.featured_video = None return if not attachment: attachment = VideoAttachment() attachment.video_id = data.get('video_id', attachment.video_id) attachment.caption = data.get('caption', None) attachment.credit = data.get('credit', None) instance_type = str(type(self)).lower() setattr(attachment, instance_type, self) attachment.save() self.featured_video = attachment def get_previous_revision(self): if self.parent == self: return self try: revision = type(self).objects \ .filter(parent=self.parent) \ .order_by('-pk')[1] return revision except: return self class Meta: abstract = True
class UploadedSingleImages(Model): # Fix pluralization in admin panel class Meta: verbose_name_plural = "Uploaded Single Images" # Define image categories to be displayed under in ~/templates/our-work.html CATEGORIES = ( ('No_Category', 'Select a Category'), ('House_Wash', 'House Wash'), ('Wood_Restoring', 'Wood Restoring'), ('Oxidation_Removal', 'Oxidation Removal'), ('Stain_Removal', 'Stain Removal'), ) DEGREES = ( (0, '0 degrees'), (270, '90 degrees (90 degrees clockwise)'), (180, '180 degrees (upside-down)'), (90, '270 degrees (90 degrees counter-clockwise)'), ) # Define the user image input fields in the Django admin panel Category = CharField(max_length=64, null=True, choices=CATEGORIES, default='No_Category') Single_Picture_Description = CharField(max_length=64, null=True, blank=True) Single_Picture_Size_kB = IntegerField(null=True, default=140) Single_Picture_Max_Dimension = IntegerField(null=True, default=768) Single_Picture_Rotation = IntegerField(null=True, choices=DEGREES, default=0) Single_Picture = ImageField(upload_to='images/', null=True) date = DateTimeField(auto_now_add=True, null=True) Notes = TextField(max_length=200, null=True, blank=True) # Add some extra functionality to the default behavior of the *.save() method # via the *.super() method def save(self, *args, **kwargs): if self.Single_Picture: # Note: this will overwrite the image uploaded by the user self.Single_Picture = self.resize_image( self.Single_Picture, self.Single_Picture_Size_kB, self.Single_Picture_Max_Dimension, self.Single_Picture_Rotation) super(UploadedSingleImages, self).save(*args, **kwargs) # Resize user-uploaded images # https://stackoverflow.com/questions/3723220/how-do-you-convert-a-pil-image-to-a-django-file def resize_image(self, picture, size_target, max_dim, rotation): # Set variables for the *.binary_search() method size_target = size_target * 1000 # Ideal image size (in bytes) dimensions = [(max_dim, max_dim)] # Dimensions for *.thumbnail() dimension_factor = 1 # For generating 1x, 2x (retina), or higher res. i = 1 # Iteration starting point max_i = 7 # Max number of iterations quality = 50 # Starting quality value L = 1 # Left pointer R = 100 # Right pointer # Run the binary search algorithm once for each set of dimensions you want to # create images at, ie. 320, 576, 768, etc. Currently there is no implementation # on the front-end to support more than one set of dimensions, but I'm keeping # the FOR loop here anyways so I know where to start if I implement multiple # dimensions later in order to support responsive images. for dimension in dimensions: im_buffer = self.binary_search(picture, size_target, dimension, dimension_factor, rotation, i, max_i, quality, L, R) # When files are uploaded in Django they are stored in a dictionary called # request.FILES as "UploadedFile" objects (or a subclass like # InMemoryUploadedFile). We can try to grab the BytesIO object and convert it # back into a File object (or "Django" File object) while the BytesIO object # is in memory, ie. while it exists within this function. # # picture.name: *.name is a Django File object attribute that includes the # name of the file plus its relative path from MEDIA_ROOT # # Syntax: # InMemoryUploadedFile(file, field_name, name, content_type, size, charset) if im_buffer is not None: im_resized_file = InMemoryUploadedFile( im_buffer, None, picture.name, 'image/jpeg', im_buffer.getbuffer().nbytes, None) return im_resized_file else: print("{} was not altered".format(picture)) return picture # Binary search algorithm that uses 3 pointers -- L, R, and quality, where the # value for quality is used by PIL's *.save() method to set the quality of an # image -- in an attempt to find a quality that produces an image with an file # size that is as close to the value for size_target as max_i number of # iterations will allow (close, but not perfect, could be memoized I think). def binary_search(self, picture, size_target, dimension, dimension_factor, rotation, i, max_i, quality, L, R, im_buffer=None): # It's possible that the picture file size is already less than the target # file size, but we can still rotate the image here. if picture.size < size_target: print("{} is already less than {} bytes".format( picture, size_target)) im = Image.open(picture) if rotation == 90: im = im.transpose(Image.ROTATE_90) elif rotation == 180: im = im.transpose(Image.ROTATE_180) elif rotation == 270: im = im.transpose(Image.ROTATE_270) im_buffer = BytesIO() im.save(im_buffer, "JPEG", quality=quality) return im_buffer # If the maximum number of iterations have been reached, return if i > max_i: print("Max iterations have been reached for {}".format(picture)) return im_buffer # Open the image file, alter its dimensions, and save it as a new BytesIO file # named 'im_buffer'. if quality <= 95: im = Image.open(picture) if rotation == 90: im = im.transpose(Image.ROTATE_90) elif rotation == 180: im = im.transpose(Image.ROTATE_180) elif rotation == 270: im = im.transpose(Image.ROTATE_270) new_dimension = (dimension[0] * dimension_factor, dimension[1] * dimension_factor) im.thumbnail(new_dimension, Image.ANTIALIAS) # new_prefix = '{}x-'.format(dimension_factor) # new_name = new_prefix + name + '-' + str(dimension[0]) + '.jpg' im_buffer = BytesIO() im.save(im_buffer, "JPEG", quality=quality) # Use L and R pointers to move closer to a value for the 'quality' parameter # that produces an image with a file size, in bytes, as close to size_target # as possible using a binary search-type of algorithm. if im_buffer.getbuffer().nbytes < size_target: print( 'Resulting image size is LESS than {} bytes:'.format( size_target), im_buffer.getbuffer().nbytes, 'bytes, quality =', quality) L = quality quality = int((R + L) / 2) return self.binary_search(picture, size_target, dimension, dimension_factor, rotation, i + 1, max_i, quality, L, R, im_buffer) elif im_buffer.getbuffer().nbytes > size_target: print( 'Resulting image size is GREATER than {} bytes:'.format( size_target), im_buffer.getbuffer().nbytes, 'bytes, quality =', quality) R = quality quality = int((R + L) / 2) return self.binary_search(picture, size_target, dimension, dimension_factor, rotation, i + 1, max_i, quality, L, R, im_buffer) else: print( 'Resulting image size EQUALS {} bytes:'.format( size_target), im_buffer.getbuffer().nbytes, 'bytes, quality =', quality) return im_buffer else: return im_buffer
class AsWKT(GeoFunc): output_field = TextField() arity = 1
class ValuesTransform(Transform): lookup_name = "values" function = "avals" output_field = ArrayField(TextField())
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 KeysTransform(Transform): lookup_name = "keys" function = "akeys" output_field = ArrayField(TextField())
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 Space(CleanSave, TimestampedModel): """A `Space`. :ivar name: The short-human-identifiable name for this space. :ivar objects: An instance of the class :class:`SpaceManager`. """ # Name of the undefined space. UNDEFINED = "undefined" class Meta(DefaultMeta): """Needed for South to recognize this model.""" verbose_name = "Space" verbose_name_plural = "Spaces" objects = SpaceManager() # We don't actually allow blank or null name, but that is enforced in # clean() and save(). name = CharField(max_length=256, editable=True, null=True, blank=True, unique=True, validators=[validate_space_name]) description = TextField(null=False, blank=True) def __str__(self): return "name=%s" % self.get_name() def is_default(self): """Is this the default space?""" return self.id == 0 def get_name(self): """Return the name of the space.""" if self.name: return self.name else: return "space-%s" % self.id def clean_name(self): reserved = re.compile('^space-\d+$') if self.name is not None and self.name != '': if self.name == Space.UNDEFINED: raise ValidationError({'name': ["Reserved space name."]}) if reserved.search(self.name): if self.id is None or self.name != 'space-%d' % self.id: raise ValidationError({'name': ["Reserved space name."]}) elif self.id is not None: # Since we are not creating the space, force the (null or empty) # name to be the default name. self.name = "space-%d" % self.id def save(self, *args, **kwargs): # Name will get set by clean_name() if None or empty, and there is an # id. We just need to handle names here for creation. super().save(*args, **kwargs) if self.name is None or self.name == '': # If we got here, then we have a newly created space that needs a # default name. self.name = "space-%d" % self.id self.save() def clean(self, *args, **kwargs): super(Space, self).clean(*args, **kwargs) self.clean_name() @property def subnet_set(self): """Backward compatibility shim to get the subnets on this space.""" # Circular imports. from maasserver.models import Subnet return Subnet.objects.filter(vlan__space=self)
class TestQuestion(Model): test = ForeignKey("Test", on_delete=CASCADE, related_name="questions") text = TextField()
class Base(CremeEntity): name = CharField(_('Name'), max_length=100) number = CharField(_('Number'), max_length=100, blank=True) issuing_date = DateField(_('Issuing date'), blank=True, null=True)\ .set_tags(optional=True) expiration_date = DateField(_('Expiration date'), blank=True, null=True)\ .set_tags(optional=True) discount = BillingDiscountField( _('Overall discount'), default=DEFAULT_DECIMAL, max_digits=10, decimal_places=2, ) billing_address = ForeignKey( settings.PERSONS_ADDRESS_MODEL, verbose_name=_('Billing address'), related_name='+', blank=True, null=True, editable=False, on_delete=SET_NULL, ).set_tags(enumerable=False) shipping_address = ForeignKey( settings.PERSONS_ADDRESS_MODEL, verbose_name=_('Shipping address'), related_name='+', blank=True, null=True, editable=False, on_delete=SET_NULL, ).set_tags(enumerable=False) currency = ForeignKey( Currency, verbose_name=_('Currency'), related_name='+', default=DEFAULT_CURRENCY_PK, on_delete=PROTECT, ) comment = TextField(_('Comment'), blank=True).set_tags(optional=True) total_vat = MoneyField( _('Total with VAT'), default=0, max_digits=14, decimal_places=2, blank=True, null=True, editable=False, ) total_no_vat = MoneyField( _('Total without VAT'), default=0, max_digits=14, decimal_places=2, blank=True, null=True, editable=False, ) additional_info = ForeignKey( AdditionalInformation, verbose_name=_('Additional Information'), related_name='+', blank=True, null=True, # on_delete=SET_NULL, on_delete=CREME_REPLACE_NULL, ).set_tags(clonable=False, optional=True) payment_terms = ForeignKey( PaymentTerms, verbose_name=_('Payment Terms'), related_name='+', blank=True, null=True, # on_delete=SET_NULL, on_delete=CREME_REPLACE_NULL, ).set_tags(clonable=False, optional=True) payment_info = ForeignKey( PaymentInformation, verbose_name=_('Payment information'), blank=True, null=True, editable=False, on_delete=SET_NULL, ).set_tags(optional=True) creation_label = _('Create an accounting document') generate_number_in_create = True # TODO: use settings instead ??? # Caches _creditnotes_cache = None class Meta: abstract = True manager_inheritance_from_future = True app_label = 'billing' ordering = ('name', ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._lines_cache = { } # Key: Line class ; Value: Lines instances (list) def __str__(self): return self.name def _pre_delete(self): lines = [*self.iter_all_lines()] for relation in Relation.objects.filter(type__in=[ REL_SUB_BILL_ISSUED, REL_SUB_BILL_RECEIVED, REL_SUB_HAS_LINE, REL_OBJ_LINE_RELATED_ITEM, ], subject_entity=self.id): relation._delete_without_transaction() for line in lines: line._delete_without_transaction() def invalidate_cache(self): self._lines_cache.clear() self._creditnotes_cache = None # TODO: property + cache # TODO: factorise with get_target() # TODO: return an Organisation instead of a CremeEntity ?? <- If doing this check calls to .get_source().get_real_entity() def get_source(self): try: return Relation.objects.get( subject_entity=self.id, type=REL_SUB_BILL_ISSUED).object_entity if self.id else None except Relation.DoesNotExist: return None def get_target(self): try: return Relation.objects.get( subject_entity=self.id, type=REL_SUB_BILL_RECEIVED).object_entity if self.id else None except Relation.DoesNotExist: return None def get_credit_notes(self): credit_notes = self._creditnotes_cache if credit_notes is None: self._creditnotes_cache = credit_notes = [] if self.id: relations = Relation.objects.filter(subject_entity=self.id, type=REL_OBJ_CREDIT_NOTE_APPLIED, ) \ .select_related('object_entity') Relation.populate_real_object_entities(relations) credit_notes.extend(rel.object_entity.get_real_entity() for rel in relations if not rel.object_entity.is_deleted) return credit_notes def generate_number(self, source=None): from creme.billing.registry import algo_registry # Lazy loading of number generators if source is None: source = self.get_source() self.number = 0 if source: real_content_type = self.entity_type try: name_algo = ConfigBillingAlgo.objects.get( organisation=source, ct=real_content_type).name_algo algo = algo_registry.get_algo(name_algo) self.number = algo().generate_number(source, real_content_type) except Exception as e: logger.debug('Exception during billing.generate_number(): %s', e) def get_lines(self, klass): assert not klass._meta.abstract, '"klass" cannot be an abstract model (use ProductLine or ServiceLine)' cache = self._lines_cache lines = cache.get(klass) if lines is None: lines = cache[klass] = klass.objects.filter( relations__object_entity=self.id, relations__type=REL_OBJ_HAS_LINE, ) return lines def iter_all_lines(self): from ..registry import lines_registry for line_cls in lines_registry: yield from self.get_lines(line_cls) def _get_lines_total_n_creditnotes_total(self): creditnotes_total = sum(credit_note.total_no_vat for credit_note in self.get_credit_notes()) lines_total = sum( l.get_price_exclusive_of_tax(self) for l in self.iter_all_lines()) return lines_total, creditnotes_total def _get_lines_total_n_creditnotes_total_with_tax(self): creditnotes_total = sum(credit_note.total_vat for credit_note in self.get_credit_notes()) lines_total_with_tax = sum( l.get_price_inclusive_of_tax(self) for l in self.iter_all_lines()) return lines_total_with_tax, creditnotes_total def _get_total(self): lines_total, creditnotes_total = self._get_lines_total_n_creditnotes_total( ) return max(DEFAULT_DECIMAL, lines_total - creditnotes_total) def _get_total_with_tax(self): lines_total_with_tax, creditnotes_total = self._get_lines_total_n_creditnotes_total_with_tax( ) return max(DEFAULT_DECIMAL, lines_total_with_tax - creditnotes_total) def _pre_save_clone(self, source): if self.generate_number_in_create: self.generate_number(source.get_source()) else: self.number = '' def _copy_relations(self, source): from ..registry import relationtype_converter # Not REL_OBJ_CREDIT_NOTE_APPLIED, links to CreditNote are not cloned. relation_create = Relation.objects.create class_map = relationtype_converter.get_class_map(source, self) super()._copy_relations( source, allowed_internal=[REL_SUB_BILL_ISSUED, REL_SUB_BILL_RECEIVED]) for relation in source.relations.filter(type__is_internal=False, type__is_copiable=True, type__in=class_map.keys()): relation_create( user_id=relation.user_id, subject_entity=self, type=class_map[relation.type], object_entity_id=relation.object_entity_id, ) def _post_clone(self, source): source.invalidate_cache() for line in source.iter_all_lines(): line.clone(self) # TODO: factorise with persons ?? def _post_save_clone(self, source): save = False if source.billing_address is not None: self.billing_address = source.billing_address.clone(self) save = True if source.shipping_address is not None: self.shipping_address = source.shipping_address.clone(self) save = True if save: self.save() # TODO: Can not we really factorise with clone() def build(self, template): self._build_object(template) self._post_save_clone(template) # Copy addresses self._post_clone(template) # Copy lines self._build_relations(template) self._build_properties(template) return self def _build_object(self, template): logger.debug("=> Clone base object") today = date.today() self.user = template.user self.name = template.name self.number = template.number self.issuing_date = today self.expiration_date = today self.discount = template.discount self.currency = template.currency self.comment = template.comment self.payment_info = template.payment_info self.save() # NB: not copied: # - additional_info # - payment_terms def _build_relations(self, template): logger.debug("=> Clone relations") self._copy_relations(template) def _build_properties(self, template): logger.debug("=> Clone properties") self._copy_properties(template) def save(self, *args, **kwargs): if self.pk: self.invalidate_cache() self.total_vat = self._get_total_with_tax() self.total_no_vat = self._get_total() return super().save(*args, **kwargs)
class simics_memory_diff(Model): checkpoint = IntegerField() block = TextField() image_index = IntegerField() result = ForeignKey(result)
def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): kwargs.pop('max_length', None) self.verify_exists = verify_exists TextField.__init__(self, verbose_name, name, **kwargs)
class injection(Model): bit = IntegerField(null=True) checkpoint = IntegerField(null=True) config_object = TextField(null=True) field = TextField(null=True) gold_value = TextField(null=True) injected_value = TextField(null=True) processor_mode = TextField(null=True) register = TextField(null=True) register_access = TextField(null=True) register_alias = TextField(null=True) register_index = ArrayField(IntegerField(), null=True) result = ForeignKey(result) success = BooleanField() target = TextField(null=True) target_index = IntegerField(null=True) target_name = TextField(null=True) time = FloatField(null=True) timestamp = DateTimeField(auto_now_add=True) tlb_entry = TextField(null=True)
class Event(CleanSave, TimestampedModel): """An `Event` represents a MAAS event. :ivar type: The event's type. :ivar node: The node of the event. :ivar node_hostname: The hostname of the node of the event. :ivar user: The user responsible for this event. :ivar username: The username of the user responsible for this event. :ivar ip_address: IP address used in the request for this event. :ivar endpoint: Endpoint used in the request for this event. :ivar user_agent: User agent used in the request for this event. :ivar action: The action of the event. :ivar description: A free-form description of the event. """ type = ForeignKey('EventType', null=False, editable=False, on_delete=PROTECT) node = ForeignKey('Node', null=True, editable=False, on_delete=SET_NULL) # Set on node deletion. node_hostname = CharField(max_length=255, default='', blank=True, validators=[validate_hostname]) user = ForeignKey(User, default=None, blank=True, null=True, editable=False, on_delete=SET_NULL) # Set on user deletion. username = CharField(max_length=32, blank=True, default='') # IP address of the request that caused this event. ip_address = MAASIPAddressField(unique=False, null=True, editable=False, blank=True, default=None) # Endpoint of request used to register the event. endpoint = IntegerField(choices=ENDPOINT_CHOICES, editable=False, default=ENDPOINT.API) # User agent of request used to register the event. user_agent = TextField(default='', blank=True, editable=False) action = TextField(default='', blank=True, editable=False) description = TextField(default='', blank=True, editable=False) objects = EventManager() class Meta(DefaultMeta): verbose_name = "Event record" index_together = (("node", "id"), ) @property def endpoint_name(self): return ENDPOINT_CHOICES[self.endpoint][1] @property def render_audit_description(self): if self.username: return self.description % {'username': self.username} else: return self.description % {'username': self.user.username} def __str__(self): return "%s (node=%s, type=%s, created=%s)" % ( self.id, self.node, self.type.name, self.created)
class EvenementAFO(CommonModel): evenement = OneToOneField(Evenement, related_name='afo', verbose_name=_('événement'), on_delete=CASCADE) nom_festival = CharField(_('nom du festival'), max_length=80, blank=True) tournee = CharField(_('code ou titre de la tournée'), max_length=60, blank=True) cycle = CharField(_('cycle'), max_length=40, blank=True) code_programme = CharField(_('code du programme'), max_length=60, blank=True) titre_programme = CharField(_('titre du programme'), max_length=200, blank=True) TYPES_DE_PROGRAMMES = ( ('LS', _('lyrique version scénique')), ('MC', _('musique de chambre')), ('LC', _('lyrique version concert')), ('S', _('symphonique (dont chœur/récital)')), ('C', _('chorégraphique')), ('A', _('autre')), ) type_de_programme = CharField(_('typologie artistique du programme'), max_length=2, blank=True, choices=TYPES_DE_PROGRAMMES) PRESENTATIONS_SPECIFIQUES = ( ('C', _('concert commenté/présenté')), ('P', _('concert participatif')), ('A', _('autre')), ) presentation_specifique = CharField(_('présentation spécifique'), max_length=1, blank=True, choices=PRESENTATIONS_SPECIFIQUES) PUBLICS_SPECIFIQUES = ( ('P', _('public de proximité')), ('E', _('public empêché (santé, handicap, justice)')), ('S', _('seniors')), ('J', _('jeunes')), ('JS', _('jeunes en temps scolaire')), ('JV', _('jeunes hors temps scolaire')), ) public_specifique = CharField(_('public spécifique'), max_length=2, blank=True, choices=PUBLICS_SPECIFIQUES) MODALITES_DE_PRODUCTION = ( ('P', _('participation aux frais')), ('A', _('autoproduction')), ('Ce', _('contrat de cession')), ('Cp', _('contrat de coproduction')), ('Cr', _('contrat de coréalisation')), ('L', _('location')), # TODO: Pas sûr que ce soit une bonne valeur. ) modalite_de_production = CharField(_('modalité de production'), max_length=2, blank=True, choices=MODALITES_DE_PRODUCTION) permanents = PositiveIntegerField( _('nombre de musiciens permanents convoqués (dont remplaçants)'), null=True, blank=True) remplacants = PositiveIntegerField(_('nombre de musiciens remplaçants'), null=True, blank=True) supplementaires = PositiveIntegerField( _('nombre de musiciens supplémentaires convoqués'), null=True, blank=True) nomenclature = TextField(_('nomenclature'), blank=True) exonerees = PositiveIntegerField(_('entrées exonérées'), null=True, blank=True) payantes = PositiveIntegerField(_('entrées payantes'), null=True, blank=True) scolaires = PositiveIntegerField(_('entrées scolaires'), null=True, blank=True) frequentation = PositiveIntegerField(_('fréquentation totale'), null=True, blank=True) jauge = PositiveIntegerField(_('jauge'), null=True, blank=True) class Meta(object): verbose_name = _('événement AFO') verbose_name_plural = _('événements AFO') def __str__(self): return force_text(self.evenement)
class ExtraDocument(Model): """Extra documents for the Income.""" image = ImageField() comment = TextField()
def resolve_all_reporters(self, info, **args): return Reporter.objects.annotate( full_name=Concat("first_name", Value(" "), "last_name", output_field=TextField()))
class Person(BasePage): resource_type = "person" parent_page_types = ["People"] subpage_types = [] template = "person.html" # Content fields nickname = CharField(max_length=250, null=True, blank=True) job_title = CharField(max_length=250) role = CharField(max_length=250, choices=ROLE_CHOICES, default="staff") description = RichTextField( "About", blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional ‘About me’ section content, supports rich text", ) image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) # 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 city = CharField(max_length=250, blank=True, default="") country = CountryField() twitter = CharField(max_length=250, blank=True, default="") facebook = CharField(max_length=250, blank=True, default="") linkedin = CharField(max_length=250, blank=True, default="") github = CharField(max_length=250, blank=True, default="") email = CharField(max_length=250, blank=True, default="") websites = StreamField( StreamBlock([("website", PersonalWebsiteBlock())], max_num=3, required=False), null=True, blank=True, help_text="Optional links to any other personal websites", ) keywords = ClusterTaggableManager(through=PersonTag, blank=True) # Content panels content_panels = [ MultiFieldPanel( [ CustomLabelFieldPanel("title", label="Full name"), FieldPanel("nickname"), FieldPanel("job_title"), FieldPanel("role"), ], heading="Details", ), 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" ), ), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ MultiFieldPanel( [FieldPanel("city"), FieldPanel("country")], heading="Location", help_text=( "Location fields. The country field is also filterable " "via the people directory page." ), ), MultiFieldPanel( [InlinePanel("topics")], heading="Topics this person specializes in" ), MultiFieldPanel( [ FieldPanel("twitter"), FieldPanel("facebook"), FieldPanel("linkedin"), FieldPanel("github"), FieldPanel("email"), ], heading="Profiles", help_text="", ), StreamFieldPanel("websites"), 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")] # Tabs 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 display_title(self): """ Return the display title for profile pages. Adds a nickname to the person's full name when one is provided. """ return f'{self.title} aka "{self.nickname}"' if self.nickname else self.title @property def events(self): """ Return upcoming events where this person is a speaker, ordered by start date """ from ..events.models import Event upcoming_events = Event.published_objects.filter( start_date__gte=datetime.datetime.now() ) speaker_events = Event.published_objects.none() for event in upcoming_events.all(): # add the event to the list if the current person is a speaker if event.has_speaker(self): speaker_events = speaker_events | Event.published_objects.page(event) return speaker_events.order_by("start_date") @property def articles(self): """ Return articles and external articles where this person is (one of) the authors, ordered by article date, most recent first """ from ..articles.models import Article from ..externalcontent.models import ExternalArticle articles = Article.published_objects.none() external_articles = ExternalArticle.published_objects.none() all_articles = Article.published_objects.all() all_external_articles = ExternalArticle.published_objects.all() for article in all_articles: if article.has_author(self): articles = articles | Article.published_objects.page(article) for external_article in all_external_articles: if external_article.has_author(self): external_articles = external_articles | ( ExternalArticle.published_objects.page(external_article) ) return sorted( chain(articles, external_articles), key=attrgetter("date"), reverse=True ) @property def videos(self): """ Return the most recent videos and external videos where this person is (one of) the speakers. """ from ..videos.models import Video from ..externalcontent.models import ExternalVideo videos = Video.published_objects.none() external_videos = ExternalVideo.published_objects.none() all_videos = Video.published_objects.all() all_external_videos = ExternalVideo.published_objects.all() for video in all_videos: if video.has_speaker(self): videos = videos | Video.published_objects.page(video) for external_video in all_external_videos: if external_video.has_speaker(self): external_videos = external_videos | ( ExternalVideo.published_objects.page(external_video) ) return sorted( chain(videos, external_videos), key=attrgetter("date"), reverse=True ) @property def role_group(self): return {"slug": self.role, "title": dict(ROLE_CHOICES).get(self.role, "")} @property def country_group(self): return ( {"slug": self.country.code.lower(), "title": self.country.name} if self.country else {"slug": ""} )
def ready(self): CharField.register_lookup(Unaccent) TextField.register_lookup(Unaccent)
from django.apps import AppConfig