class XForm(BaseModel): CLONED_SUFFIX = '_cloned' MAX_ID_LENGTH = 100 xls = models.FileField(upload_to=upload_to, null=True) json = models.TextField(default='') description = models.TextField(default='', null=True) xml = models.TextField() user = models.ForeignKey(User, related_name='xforms', null=True) require_auth = models.BooleanField(default=False) shared = models.BooleanField(default=False) shared_data = models.BooleanField(default=False) downloadable = models.BooleanField(default=True) allows_sms = models.BooleanField(default=False) encrypted = models.BooleanField(default=False) # the following fields are filled in automatically sms_id_string = models.SlugField(editable=False, verbose_name=ugettext_lazy("SMS ID"), max_length=MAX_ID_LENGTH, default='') id_string = models.SlugField(editable=False, verbose_name=ugettext_lazy("ID"), max_length=MAX_ID_LENGTH) title = models.CharField(editable=False, max_length=XFORM_TITLE_LENGTH) date_created = models.DateTimeField(auto_now_add=True) date_modified = models.DateTimeField(auto_now=True) last_submission_time = models.DateTimeField(blank=True, null=True) has_start_time = models.BooleanField(default=False) uuid = models.CharField(max_length=32, default='', db_index=True) uuid_regex = re.compile(r'(<instance>.*?id="[^"]+">)(.*</instance>)(.*)', re.DOTALL) instance_id_regex = re.compile(r'<instance>.*?id="([^"]+)".*</instance>', re.DOTALL) uuid_node_location = 2 uuid_bind_location = 4 instances_with_geopoints = models.BooleanField(default=False) num_of_submissions = models.IntegerField(default=0) tags = TaggableManager() has_kpi_hooks = LazyDefaultBooleanField(default=False) kpi_asset_uid = models.CharField(max_length=32, null=True) class Meta: app_label = 'logger' unique_together = (("user", "id_string"), ("user", "sms_id_string")) verbose_name = ugettext_lazy("XForm") verbose_name_plural = ugettext_lazy("XForms") ordering = ("id_string", ) permissions = ( (CAN_VIEW_XFORM, _('Can view associated data')), (CAN_ADD_SUBMISSIONS, _('Can make submissions to the form')), (CAN_TRANSFER_OWNERSHIP, _('Can transfer form ownership.')), (CAN_VALIDATE_XFORM, _('Can validate submissions')), (CAN_DELETE_DATA_XFORM, _('Can delete submissions')), ) def file_name(self): return self.id_string + ".xml" def url(self): return reverse("download_xform", kwargs={ "username": self.user.username, "id_string": self.id_string }) def data_dictionary(self): from onadata.apps.viewer.models.data_dictionary import\ DataDictionary return DataDictionary.objects.get(pk=self.pk) @property def has_instances_with_geopoints(self): return self.instances_with_geopoints @property def kpi_hook_service(self): """ Returns kpi hook service if it exists. XForm should have only one occurrence in any case. :return: RestService """ return self.restservices.filter(name="kpi_hook").first() def _set_id_string(self): matches = self.instance_id_regex.findall(self.xml) if len(matches) != 1: raise XLSFormError(_("There should be a single id string.")) self.id_string = matches[0] def _set_title(self): self.xml = smart_text(self.xml) text = re.sub(r'\s+', ' ', self.xml) matches = title_pattern.findall(text) title_xml = matches[0][:XFORM_TITLE_LENGTH] if len(matches) != 1: raise XLSFormError(_("There should be a single title."), matches) if self.title and title_xml != self.title: title_xml = self.title[:XFORM_TITLE_LENGTH] title_xml = saxutils.escape(title_xml) self.xml = title_pattern.sub("<h:title>%s</h:title>" % title_xml, self.xml) self.title = title_xml def _set_description(self): self.description = self.description \ if self.description and self.description != '' else self.title def _set_encrypted_field(self): if self.json and self.json != '': json_dict = json.loads(self.json) if 'submission_url' in json_dict and 'public_key' in json_dict: self.encrypted = True else: self.encrypted = False def update(self, *args, **kwargs): super(XForm, self).save(*args, **kwargs) def save(self, *args, **kwargs): self._set_title() self._set_description() old_id_string = self.id_string self._set_id_string() self._set_encrypted_field() # check if we have an existing id_string, # if so, the one must match but only if xform is NOT new if self.pk and old_id_string and old_id_string != self.id_string: raise XLSFormError( _("Your updated form's id_string '%(new_id)s' must match " "the existing forms' id_string '%(old_id)s'." % { 'new_id': self.id_string, 'old_id': old_id_string })) if getattr(settings, 'STRICT', True) and \ not re.search(r"^[\w-]+$", self.id_string): raise XLSFormError( _('In strict mode, the XForm ID must be a ' 'valid slug and contain no spaces.')) if not self.sms_id_string: try: # try to guess the form's wanted sms_id_string # from it's json rep (from XLSForm) # otherwise, use id_string to ensure uniqueness self.sms_id_string = json.loads(self.json).get( 'sms_keyword', self.id_string) except: self.sms_id_string = self.id_string super(XForm, self).save(*args, **kwargs) def __unicode__(self): return getattr(self, "id_string", "") def submission_count(self, force_update=False): if self.num_of_submissions == 0 or force_update: count = self.instances.filter(deleted_at__isnull=True).count() self.num_of_submissions = count self.save(update_fields=['num_of_submissions']) return self.num_of_submissions submission_count.short_description = ugettext_lazy("Submission Count") def geocoded_submission_count(self): """Number of geocoded submissions.""" return self.instances.filter(deleted_at__isnull=True, geom__isnull=False).count() def time_of_last_submission(self): if self.last_submission_time is None and self.num_of_submissions > 0: try: last_submission = self.instances.\ filter(deleted_at__isnull=True).latest("date_created") except ObjectDoesNotExist: pass else: self.last_submission_time = last_submission.date_created self.save() return self.last_submission_time def time_of_last_submission_update(self): try: # we also consider deleted instances in this case return self.instances.latest("date_modified").date_modified except ObjectDoesNotExist: pass @property def hash(self): return '%s' % md5(self.xml.encode('utf8')).hexdigest() @property def can_be_replaced(self): if hasattr(self.submission_count, '__call__'): num_submissions = self.submission_count() else: num_submissions = self.submission_count return num_submissions == 0 @classmethod def public_forms(cls): return cls.objects.filter(shared=True) def _xls_file_io(self): """ pulls the xls file from remote storage this should be used sparingly """ file_path = self.xls.name default_storage = get_storage_class()() if file_path != '' and default_storage.exists(file_path): with default_storage.open(file_path) as ff: if file_path.endswith('.csv'): return convert_csv_to_xls(ff.read()) else: return StringIO(ff.read()) def to_kpi_content_schema(self): """ Parses xlsform structure into json representation of spreadsheet structure. """ if not xls_to_dicts: raise ImportError('formpack module needed') content = xls_to_dicts(self._xls_file_io()) # a temporary fix to the problem of list_name alias return json.loads( re.sub('list name', 'list_name', json.dumps(content, indent=4))) def to_xlsform(self): """ Generate an XLS format XLSForm copy of this form. """ file_path = self.xls.name default_storage = get_storage_class()() if file_path != '' and default_storage.exists(file_path): with default_storage.open(file_path) as xlsform_file: if file_path.endswith('.csv'): xlsform_io = convert_csv_to_xls(xlsform_file.read()) else: xlsform_io = io.BytesIO(xlsform_file.read()) return xlsform_io else: return None @property def settings(self): """ Mimic Asset settings. :return: Object """ # As soon as we need to add custom validation statuses in Asset settings, # validation in add_validation_status_to_instance # (kobocat/onadata/apps/api/tools.py) should still work default_validation_statuses = getattr(settings, "DEFAULT_VALIDATION_STATUSES", []) # Later purpose, default_validation_statuses could be merged with a custom validation statuses dict # for example: # self._validation_statuses.update(default_validation_statuses) return {"validation_statuses": default_validation_statuses}
class Cable(ChangeLoggedModel, CustomFieldModel): """ A physical connection between two endpoints. """ termination_a_type = models.ForeignKey( to=ContentType, limit_choices_to=CABLE_TERMINATION_MODELS, on_delete=models.PROTECT, related_name='+') termination_a_id = models.PositiveIntegerField() termination_a = GenericForeignKey(ct_field='termination_a_type', fk_field='termination_a_id') termination_b_type = models.ForeignKey( to=ContentType, limit_choices_to=CABLE_TERMINATION_MODELS, on_delete=models.PROTECT, related_name='+') termination_b_id = models.PositiveIntegerField() termination_b = GenericForeignKey(ct_field='termination_b_type', fk_field='termination_b_id') type = models.CharField(max_length=50, choices=CableTypeChoices, blank=True) status = models.CharField(max_length=50, choices=CableStatusChoices, default=CableStatusChoices.STATUS_CONNECTED) label = models.CharField(max_length=100, blank=True) color = ColorField(blank=True) length = models.PositiveSmallIntegerField(blank=True, null=True) length_unit = models.CharField( max_length=50, choices=CableLengthUnitChoices, blank=True, ) # Stores the normalized length (in meters) for database ordering _abs_length = models.DecimalField(max_digits=10, decimal_places=4, blank=True, null=True) # Cache the associated device (where applicable) for the A and B terminations. This enables filtering of Cables by # their associated Devices. _termination_a_device = models.ForeignKey(to=Device, on_delete=models.CASCADE, related_name='+', blank=True, null=True) _termination_b_device = models.ForeignKey(to=Device, on_delete=models.CASCADE, related_name='+', blank=True, null=True) tags = TaggableManager(through=TaggedItem) objects = RestrictedQuerySet.as_manager() csv_headers = [ 'termination_a_type', 'termination_a_id', 'termination_b_type', 'termination_b_id', 'type', 'status', 'label', 'color', 'length', 'length_unit', ] class Meta: ordering = ['pk'] unique_together = ( ('termination_a_type', 'termination_a_id'), ('termination_b_type', 'termination_b_id'), ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # A copy of the PK to be used by __str__ in case the object is deleted self._pk = self.pk # Cache the original status so we can check later if it's been changed self._orig_status = self.status @classmethod def from_db(cls, db, field_names, values): """ Cache the original A and B terminations of existing Cable instances for later reference inside clean(). """ instance = super().from_db(db, field_names, values) instance._orig_termination_a_type_id = instance.termination_a_type_id instance._orig_termination_a_id = instance.termination_a_id instance._orig_termination_b_type_id = instance.termination_b_type_id instance._orig_termination_b_id = instance.termination_b_id return instance def __str__(self): pk = self.pk or self._pk return self.label or f'#{pk}' def get_absolute_url(self): return reverse('dcim:cable', args=[self.pk]) def clean(self): from circuits.models import CircuitTermination super().clean() # Validate that termination A exists if not hasattr(self, 'termination_a_type'): raise ValidationError('Termination A type has not been specified') try: self.termination_a_type.model_class().objects.get( pk=self.termination_a_id) except ObjectDoesNotExist: raise ValidationError({ 'termination_a': 'Invalid ID for type {}'.format(self.termination_a_type) }) # Validate that termination B exists if not hasattr(self, 'termination_b_type'): raise ValidationError('Termination B type has not been specified') try: self.termination_b_type.model_class().objects.get( pk=self.termination_b_id) except ObjectDoesNotExist: raise ValidationError({ 'termination_b': 'Invalid ID for type {}'.format(self.termination_b_type) }) # If editing an existing Cable instance, check that neither termination has been modified. if self.pk: err_msg = 'Cable termination points may not be modified. Delete and recreate the cable instead.' if (self.termination_a_type_id != self._orig_termination_a_type_id or self.termination_a_id != self._orig_termination_a_id): raise ValidationError({'termination_a': err_msg}) if (self.termination_b_type_id != self._orig_termination_b_type_id or self.termination_b_id != self._orig_termination_b_id): raise ValidationError({'termination_b': err_msg}) type_a = self.termination_a_type.model type_b = self.termination_b_type.model # Validate interface types if type_a == 'interface' and self.termination_a.type in NONCONNECTABLE_IFACE_TYPES: raise ValidationError({ 'termination_a_id': 'Cables cannot be terminated to {} interfaces'.format( self.termination_a.get_type_display()) }) if type_b == 'interface' and self.termination_b.type in NONCONNECTABLE_IFACE_TYPES: raise ValidationError({ 'termination_b_id': 'Cables cannot be terminated to {} interfaces'.format( self.termination_b.get_type_display()) }) # Check that termination types are compatible if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a): raise ValidationError( f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}" ) # Check that two connected RearPorts have the same number of positions (if both are >1) if isinstance(self.termination_a, RearPort) and isinstance( self.termination_b, RearPort): if self.termination_a.positions > 1 and self.termination_b.positions > 1: if self.termination_a.positions != self.termination_b.positions: raise ValidationError( f"{self.termination_a} has {self.termination_a.positions} position(s) but " f"{self.termination_b} has {self.termination_b.positions}. " f"Both terminations must have the same number of positions (if greater than one)." ) # A termination point cannot be connected to itself if self.termination_a == self.termination_b: raise ValidationError( f"Cannot connect {self.termination_a_type} to itself") # A front port cannot be connected to its corresponding rear port if (type_a in ['frontport', 'rearport'] and type_b in ['frontport', 'rearport'] and (getattr(self.termination_a, 'rear_port', None) == self.termination_b or getattr(self.termination_b, 'rear_port', None) == self.termination_a)): raise ValidationError( "A front port cannot be connected to it corresponding rear port" ) # Check for an existing Cable connected to either termination object if self.termination_a.cable not in (None, self): raise ValidationError( "{} already has a cable attached (#{})".format( self.termination_a, self.termination_a.cable_id)) if self.termination_b.cable not in (None, self): raise ValidationError( "{} already has a cable attached (#{})".format( self.termination_b, self.termination_b.cable_id)) # Validate length and length_unit if self.length is not None and not self.length_unit: raise ValidationError( "Must specify a unit when setting a cable length") elif self.length is None: self.length_unit = '' def save(self, *args, **kwargs): # Store the given length (if any) in meters for use in database ordering if self.length and self.length_unit: self._abs_length = to_meters(self.length, self.length_unit) else: self._abs_length = None # Store the parent Device for the A and B terminations (if applicable) to enable filtering if hasattr(self.termination_a, 'device'): self._termination_a_device = self.termination_a.device if hasattr(self.termination_b, 'device'): self._termination_b_device = self.termination_b.device super().save(*args, **kwargs) # Update the private pk used in __str__ in case this is a new object (i.e. just got its pk) self._pk = self.pk def to_csv(self): return ( '{}.{}'.format(self.termination_a_type.app_label, self.termination_a_type.model), self.termination_a_id, '{}.{}'.format(self.termination_b_type.app_label, self.termination_b_type.model), self.termination_b_id, self.get_type_display(), self.get_status_display(), self.label, self.color, self.length, self.length_unit, ) def get_status_class(self): return CableStatusChoices.CSS_CLASSES.get(self.status) def get_compatible_types(self): """ Return all termination types compatible with termination A. """ if self.termination_a is None: return return COMPATIBLE_TERMINATION_TYPES[ self.termination_a._meta.model_name]
class Page(MPTTModel): """ This model contain the status, dates, author, template. The real content of the page can be found in the :class:`Content <pages.models.Content>` model. .. attribute:: creation_date When the page has been created. .. attribute:: publication_date When the page should be visible. .. attribute:: publication_end_date When the publication of this page end. .. attribute:: last_modification_date Last time this page has been modified. .. attribute:: status The current status of the page. Could be DRAFT, PUBLISHED, EXPIRED or HIDDEN. You should the property :attr:`calculated_status` if you want that the dates are taken in account. .. attribute:: template A string containing the name of the template file for this page. """ # some class constants to refer to, e.g. Page.DRAFT DRAFT = 0 PUBLISHED = 1 EXPIRED = 2 HIDDEN = 3 STATUSES = ( (PUBLISHED, _('Published')), (HIDDEN, _('Hidden')), (DRAFT, _('Draft')), ) PAGE_LANGUAGES_KEY = "page_%d_languages" PAGE_URL_KEY = "page_%d_url" PAGE_BROKEN_LINK_KEY = "page_broken_link_%s" author = models.ForeignKey(User, verbose_name=_('author')) parent = models.ForeignKey('self', null=True, blank=True, related_name='children', verbose_name=_('parent')) creation_date = models.DateTimeField(_('creation date'), editable=False, default=datetime.now) publication_date = models.DateTimeField( _('publication date'), null=True, blank=True, help_text=_('''When the page should go live. Status must be "Published" for page to go live.''')) publication_end_date = models.DateTimeField( _('publication end date'), null=True, blank=True, help_text=_('''When to expire the page. Leave empty to never expire.''')) last_modification_date = models.DateTimeField(_('last modification date')) status = models.IntegerField(_('status'), choices=STATUSES, default=DRAFT) template = models.CharField(_('template'), max_length=100, null=True, blank=True) delegate_to = models.CharField(_('delegate to'), max_length=100, null=True, blank=True) freeze_date = models.DateTimeField(_('freeze date'), null=True, blank=True, help_text=_('''Don't publish any content after this date.''')) if settings.PAGE_USE_SITE_ID: sites = models.ManyToManyField( Site, default=[settings.SITE_ID], help_text=_('The site(s) the page is accessible at.'), verbose_name=_('sites')) redirect_to_url = models.CharField(max_length=200, null=True, blank=True) redirect_to = models.ForeignKey('self', null=True, blank=True, related_name='redirected_pages') only_authenticated_users = models.BooleanField(default=False, help_text='''If True only authenticated users can access this page''') permission = models.ForeignKey( Permission, related_name='accessible_pages', null=True, blank=True, help_text=_('''Permission needed to access the page''')) # Managers objects = PageManager() if settings.PAGE_TAGGING: tags = TaggableManager(blank=True) class Meta: """Make sure the default page ordering is correct.""" ordering = ['tree_id', 'lft'] get_latest_by = "publication_date" verbose_name = _('page') verbose_name_plural = _('pages') permissions = settings.PAGE_EXTRA_PERMISSIONS def __init__(self, *args, **kwargs): """Instanciate the page object.""" # per instance cache self._languages = None self._complete_slug = None self._content_dict = None self._is_first_root = None super(Page, self).__init__(*args, **kwargs) def save(self, *args, **kwargs): """Override the default ``save`` method.""" if not self.status: self.status = self.DRAFT # Published pages should always have a publication date if self.publication_date is None and self.status == self.PUBLISHED: self.publication_date = datetime.now() # Drafts should not, unless they have been set to the future if self.status == self.DRAFT: if settings.PAGE_SHOW_START_DATE: if (self.publication_date and self.publication_date <= datetime.now()): self.publication_date = None else: self.publication_date = None self.last_modification_date = datetime.now() # let's assume there is no more broken links after a save cache.delete(self.PAGE_BROKEN_LINK_KEY % self.id) super(Page, self).save(*args, **kwargs) # fix sites many-to-many link when the're hidden from the form if settings.PAGE_HIDE_SITES and self.sites.count() == 0: self.sites.add(Site.objects.get(pk=settings.SITE_ID)) def _get_calculated_status(self): """Get the calculated status of the page based on :attr:`Page.publication_date`, :attr:`Page.publication_end_date`, and :attr:`Page.status`.""" if settings.PAGE_SHOW_START_DATE and self.publication_date: if self.publication_date > datetime.now(): return self.DRAFT if settings.PAGE_SHOW_END_DATE and self.publication_end_date: if self.publication_end_date < datetime.now(): return self.EXPIRED return self.status calculated_status = property(_get_calculated_status) def _visible(self): """Return True if the page is visible on the frontend.""" return self.calculated_status in (self.PUBLISHED, self.HIDDEN) visible = property(_visible) def get_children_for_frontend(self): """Return a :class:`QuerySet` of published children page""" return Page.objects.filter_published(self.get_children()) def get_date_ordered_children_for_frontend(self): """Return a :class:`QuerySet` of published children page ordered by publication date.""" return self.get_children_for_frontend().order_by('-publication_date') def invalidate(self): """Invalidate cached data for this page.""" cache.delete(self.PAGE_LANGUAGES_KEY % (self.id)) cache.delete('PAGE_FIRST_ROOT_ID') self._languages = None self._complete_slug = None self._content_dict = dict() p_names = [p.name for p in get_placeholders(self.get_template())] if 'slug' not in p_names: p_names.append('slug') if 'title' not in p_names: p_names.append('title') # delete content cache, frozen or not for name in p_names: # frozen cache.delete(PAGE_CONTENT_DICT_KEY % (self.id, name, 1)) # not frozen cache.delete(PAGE_CONTENT_DICT_KEY % (self.id, name, 0)) cache.delete(self.PAGE_URL_KEY % (self.id)) def get_languages(self): """ Return a list of all used languages for this page. """ if self._languages: return self._languages self._languages = cache.get(self.PAGE_LANGUAGES_KEY % (self.id)) if self._languages is not None: return self._languages languages = [ c['language'] for c in Content.objects.filter( page=self, type="slug").values('language') ] # remove duplicates languages = list(set(languages)) languages.sort() cache.set(self.PAGE_LANGUAGES_KEY % (self.id), languages) self._languages = languages return languages def is_first_root(self): """Return ``True`` if this page is the first root pages.""" if self.parent: return False if self._is_first_root is not None: return self._is_first_root first_root_id = cache.get('PAGE_FIRST_ROOT_ID') if first_root_id is not None: self._is_first_root = first_root_id == self.id return self._is_first_root try: first_root_id = Page.objects.root().values('id')[0]['id'] except IndexError: first_root_id = None if first_root_id is not None: cache.set('PAGE_FIRST_ROOT_ID', first_root_id) self._is_first_root = self.id == first_root_id return self._is_first_root def get_url_path(self, language=None): """Return the URL's path component. Add the language prefix if ``PAGE_USE_LANGUAGE_PREFIX`` setting is set to ``True``. :param language: the wanted url language. """ if self.is_first_root(): # this is used to allow users to change URL of the root # page. The language prefix is not usable here. try: return reverse('pages-root') except Exception: pass url = self.get_complete_slug(language) if not language: language = settings.PAGE_DEFAULT_LANGUAGE if settings.PAGE_USE_LANGUAGE_PREFIX: return reverse('pages-details-by-path', args=[language, url]) else: return reverse('pages-details-by-path', args=[url]) def get_absolute_url(self, language=None): """Alias for `get_url_path`. This method is only there for backward compatibility and will be removed in a near futur. :param language: the wanted url language. """ return self.get_url_path(language=language) def get_complete_slug(self, language=None, hideroot=True): """Return the complete slug of this page by concatenating all parent's slugs. :param language: the wanted slug language.""" if not language: language = settings.PAGE_DEFAULT_LANGUAGE if self._complete_slug and language in self._complete_slug: return self._complete_slug[language] self._complete_slug = cache.get(self.PAGE_URL_KEY % (self.id)) if self._complete_slug is None: self._complete_slug = {} elif language in self._complete_slug: return self._complete_slug[language] if hideroot and settings.PAGE_HIDE_ROOT_SLUG and self.is_first_root(): url = u'' else: url = u'%s' % self.slug(language) for ancestor in self.get_ancestors(ascending=True): url = ancestor.slug(language) + u'/' + url self._complete_slug[language] = url cache.set(self.PAGE_URL_KEY % (self.id), self._complete_slug) return url def get_url(self, language=None): """Alias for `get_complete_slug`. This method is only there for backward compatibility and will be removed in a near futur. :param language: the wanted url language. """ return self.get_complete_slug(language=language) def slug(self, language=None, fallback=True): """ Return the slug of the page depending on the given language. :param language: wanted language, if not defined default is used. :param fallback: if ``True``, the slug will also be searched in other \ languages. """ slug = self.get_content(language, 'slug', language_fallback=fallback) return slug def title(self, language=None, fallback=True): """ Return the title of the page depending on the given language. :param language: wanted language, if not defined default is used. :param fallback: if ``True``, the slug will also be searched in \ other languages. """ if not language: language = settings.PAGE_DEFAULT_LANGUAGE return self.get_content(language, 'title', language_fallback=fallback) def get_content(self, language, ctype, language_fallback=False): """Shortcut method for retrieving a piece of page content :param language: wanted language, if not defined default is used. :param ctype: the type of content. :param fallback: if ``True``, the content will also be searched in \ other languages. """ return Content.objects.get_content(self, language, ctype, language_fallback) def expose_content(self): """Return all the current content of this page into a `string`. This is used by the haystack framework to build the search index.""" placeholders = get_placeholders(self.get_template()) exposed_content = [] for lang in self.get_languages(): for ctype in [p.name for p in placeholders]: content = self.get_content(lang, ctype, False) if content: exposed_content.append(content) return u"\r\n".join(exposed_content) def content_by_language(self, language): """ Return a list of latest published :class:`Content <pages.models.Content>` for a particluar language. :param language: wanted language, """ placeholders = get_placeholders(self.get_template()) content_list = [] for ctype in [p.name for p in placeholders]: try: content = Content.objects.get_content_object( self, language, ctype) content_list.append(content) except Content.DoesNotExist: pass return content_list def get_template(self): """ Get the :attr:`template <Page.template>` of this page if defined or the closer parent's one if defined or :attr:`pages.settings.PAGE_DEFAULT_TEMPLATE` otherwise. """ if self.template: return self.template template = None for p in self.get_ancestors(ascending=True): if p.template: template = p.template break if not template: template = settings.PAGE_DEFAULT_TEMPLATE return template def get_template_name(self): """ Get the template name of this page if defined or if a closer parent has a defined template or :data:`pages.settings.PAGE_DEFAULT_TEMPLATE` otherwise. """ template = self.get_template() page_templates = settings.get_page_templates() for t in page_templates: if t[0] == template: return t[1] return template def has_broken_link(self): """ Return ``True`` if the page have broken links to other pages into the content. """ return cache.get(self.PAGE_BROKEN_LINK_KEY % self.id) def valid_targets(self): """Return a :class:`QuerySet` of valid targets for moving a page into the tree. :param perms: the level of permission of the concerned user. """ exclude_list = [self.id] for p in self.get_descendants(): exclude_list.append(p.id) return Page.objects.exclude(id__in=exclude_list) def slug_with_level(self, language=None): """Display the slug of the page prepended with insecable spaces equal to simluate the level of page in the hierarchy.""" level = '' if self.level: for n in range(0, self.level): level += ' ' return mark_safe(level + self.slug(language)) def margin_level(self): """Used in the admin menu to create the left margin.""" return self.level * 2 def __unicode__(self): """Representation of the page, saved or not.""" if self.id: # without ID a slug cannot be retrieved slug = self.slug() if slug: return slug return u"Page %d" % self.id return u"Page without id"
class Job(models.Model): """ Model to create a Job. Attributes: date_created (datetime): Datetime of job creation date_modified (datetime): Datetime of job modified document (file): Job description file freelancer (user): Freelancer whom the job assigned. job_description (str): Job description job_title (str): Job title owner (user): User who owns the job price (decimal): Job price status (str): Job current status tags (str): Tags representing job """ owner = models.ForeignKey( 'users.User', on_delete=models.CASCADE, related_name='job_owner' ) freelancer = models.ForeignKey( 'users.User', null=True, blank=True, on_delete=models.CASCADE, related_name="job_freelancer" ) job_title = models.CharField(max_length=300) job_description = models.TextField() price = models.DecimalField(max_digits=8, decimal_places=2) tags = TaggableManager() date_created = models.DateTimeField(auto_now_add=True) date_modified = models.DateTimeField(auto_now=True) document = models.FileField(upload_to='attachments', blank=True, null=True) ACTIVE = 'active' WORKING = 'working' ENDED = 'ended' CHOICES = ((ACTIVE, 'active'), (WORKING, 'working'), (ENDED, 'ended')) status = models.CharField(max_length=9, choices=CHOICES, default=ACTIVE) class Meta: verbose_name = 'job' verbose_name_plural = 'jobs' unique_together = ('owner', 'date_created') def __str__(self): return "%s - %s - %s" % ( self.owner.get_full_name(), self.freelancer.get_full_name() if self.freelancer else '', self.status ) @property def freelancers(self): """ It prepares all the freelancers of the current job. """ proposals = self.job_proposal.all() return [proposal.freelancer for proposal in proposals]
class Post(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1) title = models.CharField(max_length=120) slug = models.SlugField(unique=True) image = models.ImageField(upload_to=upload_location, null=True, blank=True, width_field="width_field", height_field="height_field") likes = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='post_likes') height_field = models.IntegerField(default=0) width_field = models.IntegerField(default=0) content = models.TextField() draft = models.BooleanField(default=False) publish = models.DateField(auto_now=False, auto_now_add=False) updated = models.DateTimeField(auto_now=True, auto_now_add=False) timestamp = models.DateTimeField(auto_now=False, auto_now_add=True) community = models.ForeignKey(Communities, on_delete=models.CASCADE) language = models.CharField(max_length=120, blank=True) source = models.CharField(max_length=120, blank=True) summary = models.CharField(max_length=120, blank=True) tags = TaggableManager(blank=True) def __unicode__(self): return self.name objects = PostManager() def __unicode__(self): return self.title def __str__(self): return self.title def get_absolute_url(self): return reverse("posts:detail", kwargs={"slug": self.slug}) # def get_api_url(self): # return reverse("posts-api:detail", kwargs={"slug": self.slug}) def get_like_url(self): return reverse("posts:like-toggle", kwargs={"slug": self.slug}) def get_api_like_url(self): return reverse("posts:like-api-toggle", kwargs={"slug": self.slug}) class Meta: ordering = ["-timestamp", "-updated"] @property def comments(self): instance = self qs = Comment.objects.filter_by_instance(instance) return qs @property def communities(self): instance = self qs = Communities.objects.filter_by_instance(instance) return qs @property def get_content_type(self): instance = self content_type = ContentType.objects.get_for_model(instance.__class__) return content_type
class Issue(GcdData): class Meta: app_label = 'gcd' ordering = ['series', 'sort_code'] unique_together = ('series', 'sort_code') # Issue identification number = models.CharField(max_length=50, db_index=True) title = models.CharField(max_length=255, db_index=True) no_title = models.BooleanField(default=False, db_index=True) volume = models.CharField(max_length=50, db_index=True) no_volume = models.BooleanField(default=False, db_index=True) volume_not_printed = models.BooleanField(default=False) display_volume_with_number = models.BooleanField(default=False, db_index=True) isbn = models.CharField(max_length=32, db_index=True) no_isbn = models.BooleanField(default=False, db_index=True) valid_isbn = models.CharField(max_length=13, db_index=True) variant_of = models.ForeignKey('self', null=True, related_name='variant_set') variant_name = models.CharField(max_length=255) barcode = models.CharField(max_length=38, db_index=True) no_barcode = models.BooleanField(default=False) rating = models.CharField(max_length=255, default='', db_index=True) no_rating = models.BooleanField(default=False, db_index=True) # Dates and sorting publication_date = models.CharField(max_length=255) key_date = models.CharField(max_length=10, db_index=True) on_sale_date = models.CharField(max_length=10, db_index=True) on_sale_date_uncertain = models.BooleanField(default=False) sort_code = models.IntegerField(db_index=True) indicia_frequency = models.CharField(max_length=255) no_indicia_frequency = models.BooleanField(default=False, db_index=True) # Price, page count and format fields price = models.CharField(max_length=255) page_count = models.DecimalField(max_digits=10, decimal_places=3, null=True) page_count_uncertain = models.BooleanField(default=False) editing = models.TextField() no_editing = models.BooleanField(default=False, db_index=True) notes = models.TextField() keywords = TaggableManager() # Series and publisher links series = models.ForeignKey('Series') indicia_publisher = models.ForeignKey(IndiciaPublisher, null=True) indicia_pub_not_printed = models.BooleanField(default=False) brand = models.ForeignKey(Brand, null=True) no_brand = models.BooleanField(default=False, db_index=True) image_resources = GenericRelation(Image) # In production, this is a tinyint(1) because the set of numbers # is very small. But syncdb produces an int(11). is_indexed = models.IntegerField(default=0, db_index=True) @property def indicia_image(self): img = Image.objects.filter( object_id=self.id, deleted=False, content_type=ContentType.objects.get_for_model(self), type__id=1) if img: return img.get() else: return None @property def soo_image(self): img = Image.objects.filter( object_id=self.id, deleted=False, content_type=ContentType.objects.get_for_model(self), type__id=2) if img: return img.get() else: return None def active_stories(self): return self.story_set.exclude(deleted=True) def _active_variants(self): return self.variant_set.exclude(deleted=True) def active_variants(self): return self._active_variants() def shown_stories(self): """ returns cover sequence and story sequences """ if self.variant_of: stories_from = self.variant_of else: stories_from = self stories = list(stories_from.active_stories().order_by( 'sequence_number').select_related('type', 'migration_status')) if self.series.is_comics_publication: if (len(stories) > 0): cover_story = stories.pop(0) if self.variant_of: # can have only one sequence, the variant cover if self.active_stories().count(): cover_story = self.active_stories()[0] elif self.variant_of and len(list(self.active_stories())): cover_story = self.active_stories()[0] else: cover_story = None else: cover_story = None return cover_story, stories def _active_covers(self): if self.can_have_cover(): return self.cover_set.exclude(deleted=True) else: return self.cover_set.none() def active_covers(self): return self._active_covers() def variant_covers(self): """ returns the images from the variant issues """ from .cover import Cover if self.variant_of: variant_issues = list(self.variant_of.active_variants().exclude( id=self.id).values_list('id', flat=True)) else: variant_issues = list(self.active_variants().values_list( 'id', flat=True)) variant_covers = Cover.objects.filter(issue__id__in=variant_issues)\ .exclude(deleted=True) if self.variant_of: variant_covers |= self.variant_of.active_covers() return variant_covers def shown_covers(self): return self.active_covers(), self.variant_covers() def has_covers(self): return self.can_have_cover() and self.active_covers().exists() def can_have_cover(self): if self.series.is_comics_publication: return True if self.is_indexed in [INDEXED['full'], INDEXED['ten_percent']]: return True else: return False def other_variants(self): if self.variant_of: variants = self.variant_of.active_variants().exclude(id=self.id) else: variants = self.active_variants() return list(variants) def _get_prev_next_issue(self): """ Find the issues immediately before and after the given issue. """ prev_issue = None next_issue = None earlier_issues = self.series.active_base_issues()\ .filter(sort_code__lt=self.sort_code) earlier_issues = earlier_issues.order_by('-sort_code') if earlier_issues: prev_issue = earlier_issues[0] later_issues = self.series.active_base_issues()\ .filter(sort_code__gt=self.sort_code) later_issues = later_issues.order_by('sort_code') if later_issues: next_issue = later_issues[0] return [prev_issue, next_issue] def get_prev_next_issue(self): return self._get_prev_next_issue() def has_reprints(self, ignore=STORY_TYPES['preview']): """Simplifies UI checks for conditionals, notes and reprint fields""" return self.from_reprints.count() or \ self.to_reprints.exclude(target__type__id=ignore).count() or \ self.from_issue_reprints.count() or \ self.to_issue_reprints.count() def has_variants(self): return self.active_variants().exists() def has_dependents(self): has_non_story_deps = ( self.has_variants() or self.has_reprints(ignore=None) or self.cover_revisions.active_set().exists() or self.variant_revisions.active_set().exists() or self.origin_reprint_revisions.active_set().exists() or self.target_reprint_revisions.active_set().exists()) if has_non_story_deps: return True for story in self.active_stories(): has_story_deps = ( story.has_reprints(notes=False) or story.origin_reprint_revisions.active_set().exists() or story.target_reprint_revisions.active_set().exists()) if has_story_deps: return True return False def can_upload_variants(self): if self.has_covers(): currently_deleting = self.revisions.active_set() \ .filter(deleted=True).exists() return not currently_deleting else: return False def set_indexed_status(self): """ Sets the index status and returns the resulting stat change value. The return value of this method is intended for use in adjusting the "issue indexes" stat count. GCD model modules cannot import CountStats and set them directly due to circular dependencies. """ was_indexed = self.is_indexed if not self.variant_of: is_indexed = INDEXED['skeleton'] if self.page_count > 0: total_count = self.active_stories()\ .aggregate(Sum('page_count'))['page_count__sum'] if (total_count > 0 and total_count >= Decimal('0.4') * self.page_count): is_indexed = INDEXED['full'] elif (total_count > 0 and total_count >= Decimal('0.1') * self.page_count): is_indexed = INDEXED['ten_percent'] if (is_indexed not in [INDEXED['full'], INDEXED['ten_percent']] and self.active_stories().filter(type=StoryType.objects.get( name='comic story')).exists()): is_indexed = INDEXED['partial'] if is_indexed == INDEXED['full']: if self.page_count_uncertain or self.active_stories()\ .filter(page_count_uncertain=True).exists(): is_indexed = INDEXED['partial'] if self.is_indexed != is_indexed: self.is_indexed = is_indexed self.save() if self.active_variants(): for variant in self.active_variants(): variant.is_indexed = is_indexed variant.save() index_delta = 0 if self.series.is_comics_publication: if not was_indexed and self.is_indexed: index_delta = 1 elif was_indexed and not self.is_indexed: index_delta = -1 return index_delta _update_stats = True def stat_counts(self): """ Returns all count values relevant to this issue. Includes counts for the issue itself. Non-comics publications return statistics only for stories and covers, as non-comics issues do not count towards stats. Note that we have a special value "series issues", because non-variant issues are counted differently with respect to series than in general. A series always counts its own non-variant issues, even when the series is not a comics publication. """ if self.deleted: return {} counts = { 'stories': self.active_stories().count(), 'covers': self.active_covers().count(), } if not self.variant_of_id: counts['series issues'] = 1 if self.series.is_comics_publication: if self.variant_of_id: counts['variant issues'] = 1 else: counts['issues'] = 1 if self.is_indexed != INDEXED['skeleton']: counts['issue indexes'] = 1 return counts def get_absolute_url(self): return urlresolvers.reverse('show_issue', kwargs={'issue_id': self.id}) @property def full_descriptor(self): if self.variant_name: return "%s [%s]" % (self.issue_descriptor, self.variant_name) else: return self.issue_descriptor @property def issue_descriptor(self): return issue_descriptor(self) @property def display_full_descriptor(self): number = self.full_descriptor if number: return u'#' + number else: return u'' @property def display_number(self): number = self.issue_descriptor if number: return u'#' + number else: return u'' def full_name(self, variant_name=True): if variant_name and self.variant_name: return u'%s %s [%s]' % (self.series.full_name(), self.display_number, self.variant_name) else: return u'%s %s' % (self.series.full_name(), self.display_number) def full_name_with_link(self, publisher=False): name_link = self.series.full_name_with_link(publisher) return mark_safe( '%s <a href="%s">%s</a>' % (name_link, self.get_absolute_url(), esc(self.display_number))) def short_name(self): if self.variant_name: return u'%s %s [%s]' % (self.series.name, self.display_number, self.variant_name) else: return u'%s %s' % (self.series.name, self.display_number) def __unicode__(self): if self.variant_name: return u'%s %s [%s]' % (self.series, self.display_number, self.variant_name) else: return u'%s %s' % (self.series, self.display_number)
class XForm(XFormMixin, BaseModel): CLONED_SUFFIX = '_cloned' MAX_ID_LENGTH = 100 xls = models.FileField(upload_to=upload_to, null=True) json = models.TextField(default=u'') description = models.TextField(default=u'', null=True, blank=True) xml = models.TextField() user = models.ForeignKey(User, related_name='xforms', null=True) require_auth = models.BooleanField(default=False) shared = models.BooleanField(default=False) shared_data = models.BooleanField(default=False) downloadable = models.BooleanField(default=True) allows_sms = models.BooleanField(default=False) encrypted = models.BooleanField(default=False) deleted_by = models.ForeignKey(User, related_name='xform_deleted_by', null=True, on_delete=models.SET_NULL, default=None, blank=True) # the following fields are filled in automatically sms_id_string = models.SlugField( editable=False, verbose_name=ugettext_lazy("SMS ID"), max_length=MAX_ID_LENGTH, default='') id_string = models.SlugField( editable=False, verbose_name=ugettext_lazy("ID"), max_length=MAX_ID_LENGTH) title = models.CharField(editable=False, max_length=XFORM_TITLE_LENGTH) date_created = models.DateTimeField(auto_now_add=True) date_modified = models.DateTimeField(auto_now=True) deleted_at = models.DateTimeField(blank=True, null=True) last_submission_time = models.DateTimeField(blank=True, null=True) has_start_time = models.BooleanField(default=False) uuid = models.CharField(max_length=36, default=u'') uuid_regex = re.compile(r'(<instance>.*?id="[^"]+">)(.*</instance>)(.*)', re.DOTALL) instance_id_regex = re.compile(r'<instance>.*?id="([^"]+)".*</instance>', re.DOTALL) uuid_node_location = 2 uuid_bind_location = 4 bamboo_dataset = models.CharField(max_length=60, default=u'') instances_with_geopoints = models.BooleanField(default=False) instances_with_osm = models.BooleanField(default=False) num_of_submissions = models.IntegerField(default=0) version = models.CharField( max_length=XFORM_TITLE_LENGTH, null=True, blank=True) project = models.ForeignKey('Project') created_by = models.ForeignKey(User, null=True, blank=True) metadata_set = GenericRelation( 'main.MetaData', content_type_field='content_type_id', object_id_field="object_id") has_hxl_support = models.BooleanField(default=False) last_updated_at = models.DateTimeField(auto_now=True) hash = models.CharField(_("Hash"), max_length=36, blank=True, null=True, default=None) # XForm was created as a merged dataset is_merged_dataset = models.BooleanField(default=False) tags = TaggableManager() class Meta: app_label = 'logger' unique_together = (("user", "id_string", "project"), ("user", "sms_id_string", "project")) verbose_name = ugettext_lazy("XForm") verbose_name_plural = ugettext_lazy("XForms") ordering = ("pk", ) permissions = ( ("view_xform", _("Can view associated data")), ("view_xform_all", _("Can view all associated data")), ("view_xform_data", _("Can view submitted data")), ("report_xform", _("Can make submissions to the form")), ("move_xform", _(u"Can move form between projects")), ("transfer_xform", _(u"Can transfer form ownership.")), ("can_export_xform_data", _(u"Can export form data")), ("delete_submission", _(u"Can delete submissions from form")), ) def file_name(self): return self.id_string + ".xml" def url(self): return reverse( "download_xform", kwargs={ "username": self.user.username, "id_string": self.id_string }) @property def has_instances_with_geopoints(self): return self.instances_with_geopoints def _set_id_string(self): matches = self.instance_id_regex.findall(self.xml) if len(matches) != 1: raise XLSFormError(_("There should be a single id string.")) self.id_string = matches[0] def _set_title(self): xml = re.sub(r"\s+", " ", self.xml) matches = title_pattern.findall(xml) if len(matches) != 1: raise XLSFormError(_("There should be a single title."), matches) if matches: title_xml = matches[0][:XFORM_TITLE_LENGTH] else: title_xml = self.title[:XFORM_TITLE_LENGTH] if self.title else '' if self.title and title_xml != self.title: title_xml = self.title[:XFORM_TITLE_LENGTH] if isinstance(self.xml, b): self.xml = self.xml.decode('utf-8') self.xml = title_pattern.sub(u"<h:title>%s</h:title>" % title_xml, self.xml) self._set_hash() if contains_xml_invalid_char(title_xml): raise XLSFormError( _("Title shouldn't have any invalid xml " "characters ('>' '&' '<')")) self.title = title_xml def _set_hash(self): self.hash = self.get_hash() def _set_encrypted_field(self): if self.json and self.json != '': json_dict = json.loads(self.json) if 'submission_url' in json_dict and 'public_key' in json_dict: self.encrypted = True else: self.encrypted = False def update(self, *args, **kwargs): super(XForm, self).save(*args, **kwargs) def save(self, *args, **kwargs): update_fields = kwargs.get('update_fields') if update_fields: kwargs['update_fields'] = list( set(list(update_fields) + ['date_modified'])) if update_fields is None or 'title' in update_fields: self._set_title() if self.pk is None: self._set_hash() if update_fields is None or 'encrypted' in update_fields: self._set_encrypted_field() if update_fields is None or 'id_string' in update_fields: old_id_string = self.id_string if not self.deleted_at: self._set_id_string() # check if we have an existing id_string, # if so, the one must match but only if xform is NOT new if self.pk and old_id_string and old_id_string != self.id_string \ and self.num_of_submissions > 0: raise XLSFormError( _(u"Your updated form's id_string '%(new_id)s' must match " "the existing forms' id_string '%(old_id)s'." % {'new_id': self.id_string, 'old_id': old_id_string})) if getattr(settings, 'STRICT', True) and \ not re.search(r"^[\w-]+$", self.id_string): raise XLSFormError( _(u'In strict mode, the XForm ID must be a ' 'valid slug and contain no spaces.')) if not self.sms_id_string and (update_fields is None or 'id_string' in update_fields): try: # try to guess the form's wanted sms_id_string # from it's json rep (from XLSForm) # otherwise, use id_string to ensure uniqueness self.sms_id_string = json.loads(self.json).get( 'sms_keyword', self.id_string) except Exception: self.sms_id_string = self.id_string if 'skip_xls_read' in kwargs: del kwargs['skip_xls_read'] super(XForm, self).save(*args, **kwargs) def __str__(self): return getattr(self, "id_string", "") def soft_delete(self, user=None): """ Return the soft deletion timestamp Mark the XForm as soft deleted, appending a timestamped suffix to the id_string and sms_id_string to make the initial values available without violating the uniqueness constraint. Also soft deletes associated dataviews """ soft_deletion_time = timezone.now() deletion_suffix = soft_deletion_time.strftime('-deleted-at-%s') self.deleted_at = soft_deletion_time self.id_string += deletion_suffix self.sms_id_string += deletion_suffix self.downloadable = False update_fields = ['date_modified', 'deleted_at', 'id_string', 'sms_id_string', 'downloadable'] if user is not None: self.deleted_by = user update_fields.append('deleted_by') self.save(update_fields=update_fields) for dataview in self.dataview_set.all(): dataview.soft_delete(user) def submission_count(self, force_update=False): if self.num_of_submissions == 0 or force_update: if self.is_merged_dataset: count = self.mergedxform.xforms.aggregate( num=Sum('num_of_submissions')).get('num') else: count = self.instances.filter(deleted_at__isnull=True).count() if count != self.num_of_submissions: self.num_of_submissions = count self.save(update_fields=['num_of_submissions']) # clear cache key = '{}{}'.format(XFORM_COUNT, self.pk) safe_delete(key) return self.num_of_submissions submission_count.short_description = ugettext_lazy("Submission Count") @property def submission_count_for_today(self): current_timzone_name = timezone.get_current_timezone_name() current_timezone = pytz.timezone(current_timzone_name) today = datetime.today() current_date = current_timezone.localize( datetime(today.year, today.month, today.day)) count = self.instances.filter( deleted_at__isnull=True, date_created=current_date).count() return count def geocoded_submission_count(self): """Number of geocoded submissions.""" return self.instances.filter( deleted_at__isnull=True, geom__isnull=False).count() def time_of_last_submission(self): if self.last_submission_time is None and self.num_of_submissions > 0: try: last_submission = self.instances.\ filter(deleted_at__isnull=True).latest("date_created") except ObjectDoesNotExist: pass else: self.last_submission_time = last_submission.date_created self.save() return self.last_submission_time def time_of_last_submission_update(self): try: # we also consider deleted instances in this case return self.instances.latest("date_modified").date_modified except ObjectDoesNotExist: pass def get_hash(self): return u'md5:%s' % md5(self.xml.encode('utf8')).hexdigest() @property def can_be_replaced(self): return self.num_of_submissions == 0 @classmethod def public_forms(cls): return cls.objects.filter(shared=True)
class Service(ChangeLoggedModel, CustomFieldModel): """ A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may optionally be tied to one or more specific IPAddresses belonging to its parent. """ device = models.ForeignKey(to='dcim.Device', on_delete=models.CASCADE, related_name='services', verbose_name='device', null=True, blank=True) virtual_machine = models.ForeignKey(to='virtualization.VirtualMachine', on_delete=models.CASCADE, related_name='services', null=True, blank=True) name = models.CharField(max_length=30) protocol = models.PositiveSmallIntegerField(choices=IP_PROTOCOL_CHOICES) port = models.PositiveIntegerField( validators=[MinValueValidator(1), MaxValueValidator(65535)], verbose_name='Port number') ipaddresses = models.ManyToManyField(to='ipam.IPAddress', related_name='services', blank=True, verbose_name='IP addresses') description = models.CharField(max_length=100, blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') tags = TaggableManager() csv_headers = [ 'device', 'virtual_machine', 'name', 'protocol', 'description' ] class Meta: ordering = ['protocol', 'port'] def __str__(self): return '{} ({}/{})'.format(self.name, self.port, self.get_protocol_display()) def get_absolute_url(self): return reverse('ipam:service', args=[self.pk]) @property def parent(self): return self.device or self.virtual_machine def clean(self): # A Service must belong to a Device *or* to a VirtualMachine if self.device and self.virtual_machine: raise ValidationError( "A service cannot be associated with both a device and a virtual machine." ) if not self.device and not self.virtual_machine: raise ValidationError( "A service must be associated with either a device or a virtual machine." ) def to_csv(self): return ( self.device.name if self.device else None, self.virtual_machine.name if self.virtual_machine else None, self.name, self.get_protocol_display(), self.port, self.description, )
class Project(models.Model): #Auto fields pub_date = models.DateTimeField(_('Publication date'), auto_now_add=True) modified_date = models.DateTimeField(_('Modified date'), auto_now=True) #Generally from conf.py users = models.ManyToManyField(User, verbose_name=_('User'), related_name='projects') name = models.CharField(_('Name'), max_length=255) slug = models.SlugField(_('Slug'), max_length=255, unique=True) description = models.TextField(_('Description'), blank=True, help_text=_('The reStructuredText ' 'description of the project')) repo = models.CharField(_('Repository URL'), max_length=100, blank=True, help_text=_('Checkout URL for your code (hg, git, ' 'etc.). Ex. http://github.com/' 'ericholscher/django-kong.git')) repo_type = models.CharField(_('Repository type'), max_length=10, choices=constants.REPO_CHOICES, default='git') project_url = models.URLField(_('Project URL'), blank=True, help_text=_('The project\'s homepage'), verify_exists=False) version = models.CharField(_('Version'), max_length=100, blank=True, help_text=_('Project version these docs apply ' 'to, i.e. 1.0a')) copyright = models.CharField(_('Copyright'), max_length=255, blank=True, help_text=_('Project copyright information')) theme = models.CharField( _('Theme'), max_length=20, choices=constants.DEFAULT_THEME_CHOICES, default=constants.THEME_DEFAULT, help_text=(u'<a href="http://sphinx.pocoo.org/theming.html#builtin-' 'themes" target="_blank">%s</a>') % _('Examples')) suffix = models.CharField(_('Suffix'), max_length=10, editable=False, default='.rst') default_version = models.CharField( _('Default version'), max_length=255, default='latest', help_text=_('The version of your project that / redirects to')) # In default_branch, None max_lengtheans the backend should choose the # appropraite branch. Eg 'master' for git default_branch = models.CharField( _('Default branch'), max_length=255, default=None, null=True, blank=True, help_text=_('What branch "latest" points to. Leave empty ' 'to use the default value for your VCS (eg. ' 'trunk or master).')) requirements_file = models.CharField( _('Requirements file'), max_length=255, default=None, null=True, blank=True, help_text=_( 'Requires Virtualenv. A <a ' 'href="http://www.pip-installer.org/en/latest/requirements.html">' 'pip requirements file</a> needed to build your documentation. ' 'Path from the root of your project.')) documentation_type = models.CharField( _('Documentation type'), max_length=20, choices=constants.DOCUMENTATION_CHOICES, default='sphinx', help_text=_('Type of documentation you are building. <a href="http://' 'sphinx.pocoo.org/builders.html#sphinx.builders.html.' 'DirectoryHTMLBuilder">More info</a>.')) analytics_code = models.CharField( _('Analytics code'), max_length=50, null=True, blank=True, help_text=_("Google Analytics Tracking ID (ex. UA-22345342-1). " "This may slow down your page loads.")) # Other model data. path = models.CharField(_('Path'), max_length=255, editable=False, help_text=_("The directory where conf.py lives")) conf_py_file = models.CharField( _('Python configuration file'), max_length=255, default='', blank=True, help_text=_('Path from project root to conf.py file (ex. docs/conf.py)' '. Leave blank if you want us to find it for you.')) featured = models.BooleanField(_('Featured')) skip = models.BooleanField(_('Skip')) use_virtualenv = models.BooleanField( _('Use virtualenv'), help_text=_("Install your project inside a virtualenv using setup.py " "install")) # This model attribute holds the python interpreter used to create the # virtual environment python_interpreter = models.CharField( _('Python Interpreter'), max_length=20, choices=constants.PYTHON_CHOICES, default='python', help_text=_("(Beta) The Python interpreter used to create the virtual " "environment.")) use_system_packages = models.BooleanField( _('Use system packages'), help_text=_("Give the virtual environment access to the global " "sites-packages dir")) django_packages_url = models.CharField(_('Django Packages URL'), max_length=255, blank=True) crate_url = models.CharField(_('Crate URL'), max_length=255, blank=True) privacy_level = models.CharField( _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, default='public', help_text=_("(Beta) Level of privacy that you want on the repository. " "Protected means public but not in listings.")) version_privacy_level = models.CharField( _('Version Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, default='public', help_text=_("(Beta) Default level of privacy you want on built " "versions of documentation.")) # Subprojects related_projects = models.ManyToManyField( 'self', verbose_name=_('Related projects'), blank=True, null=True, symmetrical=False, through=ProjectRelationship) # Language bits language = models.CharField('Language', max_length=20, default='en', help_text="The language the project " "documentation is rendered in", choices=constants.LANGUAGES) # A subproject pointed at it's main language, so it can be tracked main_language_project = models.ForeignKey('self', related_name='translations', blank=True, null=True) tags = TaggableManager(blank=True) objects = ProjectManager() class Meta: ordering = ('slug',) permissions = ( # Translators: Permission around whether a user can view the # project ('view_project', _('View Project')), ) def __unicode__(self): return self.name @property def subdomain(self): prod_domain = getattr(settings, 'PRODUCTION_DOMAIN') subdomain_slug = self.slug.replace('_', '-') return "%s.%s" % (subdomain_slug, prod_domain) def save(self, *args, **kwargs): #if hasattr(self, 'pk'): #previous_obj = self.__class__.objects.get(pk=self.pk) #if previous_obj.repo != self.repo: #Needed to not have an import loop on Project #from projects import tasks #This needs to run on the build machine. #tasks.remove_dir.delay(os.path.join(self.doc_path, #'checkouts')) if not self.slug: self.slug = slugify(self.name) if self.slug == '': raise Exception(_("Model must have slug")) obj = super(Project, self).save(*args, **kwargs) for owner in self.users.all(): assign('view_project', owner, self) return obj def get_absolute_url(self): return reverse('projects_detail', args=[self.slug]) def get_docs_url(self, version_slug=None, lang_slug=None): """ Return a url for the docs. Always use http for now, to avoid content warnings. """ protocol = "http" version = version_slug or self.get_default_version() use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False) if not lang_slug: lang_slug = self.language if use_subdomain: return "%s://%s/%s/%s/" % ( protocol, self.subdomain, lang_slug, version, ) else: return reverse('docs_detail', kwargs={ 'project_slug': self.slug, 'lang_slug': lang_slug, 'version_slug': version, 'filename': '' }) def get_translation_url(self, version_slug=None): parent = self.main_language_project lang_slug = self.language protocol = "http" version = version_slug or parent.get_default_version() use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False) if use_subdomain: return "%s://%s/%s/%s/" % ( protocol, parent.subdomain, lang_slug, version, ) else: return reverse('docs_detail', kwargs={ 'project_slug': parent.slug, 'lang_slug': lang_slug, 'version_slug': version, 'filename': '' }) def get_builds_url(self): return reverse('builds_project_list', kwargs={ 'project_slug': self.slug, }) def get_pdf_url(self, version_slug='latest'): path = os.path.join(settings.MEDIA_URL, 'pdf', self.slug, version_slug, '%s.pdf' % self.slug) return path def get_pdf_path(self, version_slug='latest'): path = os.path.join(settings.MEDIA_ROOT, 'pdf', self.slug, version_slug, '%s.pdf' % self.slug) return path def get_epub_url(self, version_slug='latest'): path = os.path.join(settings.MEDIA_URL, 'epub', self.slug, version_slug, '%s.epub' % self.slug) return path def get_epub_path(self, version_slug='latest'): path = os.path.join(settings.MEDIA_ROOT, 'epub', self.slug, version_slug, '%s.epub' % self.slug) return path def get_manpage_url(self, version_slug='latest'): path = os.path.join(settings.MEDIA_URL, 'man', self.slug, version_slug, '%s.1' % self.slug) return path def get_manpage_path(self, version_slug='latest'): path = os.path.join(settings.MEDIA_ROOT, 'man', self.slug, version_slug, '%s.1' % self.slug) return path def get_htmlzip_url(self, version_slug='latest'): path = os.path.join(settings.MEDIA_URL, 'htmlzip', self.slug, version_slug, '%s.zip' % self.slug) return path def get_htmlzip_path(self, version_slug='latest'): path = os.path.join(settings.MEDIA_ROOT, 'htmlzip', self.slug, version_slug, '%s.zip' % self.slug) return path def get_dash_url(self, version_slug='latest'): path = os.path.join(settings.MEDIA_URL, 'dash', self.slug, version_slug, '%s.tgz' % self.doc_name) return path def get_dash_path(self, version_slug='latest'): path = os.path.join(settings.MEDIA_ROOT, 'dash', self.slug, version_slug, '%s.tgz' % self.doc_name) return path def get_dash_feed_path(self, version_slug='latest'): path = os.path.join(settings.MEDIA_ROOT, 'dash', self.slug, version_slug, '%s.xml' % self.doc_name) return path def get_dash_feed_url(self, version_slug='latest'): path = os.path.join(settings.MEDIA_URL, 'dash', self.slug, version_slug, '%s.xml' % self.doc_name) return path @property def doc_name(self): return self.name.replace(' ', '_') #Doc PATH: #MEDIA_ROOT/slug/checkouts/version/<repo> @property def doc_path(self): return os.path.join(settings.DOCROOT, self.slug) def checkout_path(self, version='latest'): return os.path.join(self.doc_path, 'checkouts', version) def venv_path(self, version='latest'): return os.path.join(self.doc_path, 'envs', version) def translations_path(self, language=None): if not language: language = self.language return os.path.join(self.doc_path, 'translations', language) def venv_bin(self, version='latest', bin='python'): return os.path.join(self.venv_path(version), 'bin', bin) def full_doc_path(self, version='latest'): """ The path to the documentation root in the project. """ doc_base = self.checkout_path(version) for possible_path in ['docs', 'doc', 'Doc']: if os.path.exists(os.path.join(doc_base, '%s' % possible_path)): return os.path.join(doc_base, '%s' % possible_path) #No docs directory, docs are at top-level. return doc_base def full_build_path(self, version='latest'): """ The path to the build html docs in the project. """ return os.path.join(self.conf_dir(version), "_build", "html") def full_latex_path(self, version='latest'): """ The path to the build latex docs in the project. """ return os.path.join(self.conf_dir(version), "_build", "latex") def full_man_path(self, version='latest'): """ The path to the build latex docs in the project. """ return os.path.join(self.conf_dir(version), "_build", "man") def full_epub_path(self, version='latest'): """ The path to the build latex docs in the project. """ return os.path.join(self.conf_dir(version), "_build", "epub") def full_dash_path(self, version='latest'): """ The path to the build dash docs in the project. """ return os.path.join(self.conf_dir(version), "_build", "dash") def rtd_build_path(self, version="latest"): """ The path to the build html docs in the project. """ return os.path.join(self.doc_path, 'rtd-builds', version) def rtd_cname_path(self, cname): """ The path to the build html docs in the project. """ return os.path.join(settings.CNAME_ROOT, cname) def conf_file(self, version='latest'): if self.conf_py_file: log.debug('Inserting conf.py file path from model') return os.path.join(self.checkout_path(version), self.conf_py_file) files = self.find('conf.py', version) if not files: files = self.full_find('conf.py', version) if len(files) == 1: return files[0] elif len(files) > 1: for file in files: if file.find('doc', 70) != -1: return file else: raise ProjectImportError(_("Conf File Missing.")) def conf_dir(self, version='latest'): conf_file = self.conf_file(version) if conf_file: return conf_file.replace('/conf.py', '') @property def highest_version(self): return _highest(self.api_versions()) @property def is_imported(self): return bool(self.repo) @property def has_good_build(self): return self.builds.filter(success=True).exists() @property def has_versions(self): return self.versions.exists() @property def has_aliases(self): return self.aliases.exists() def has_pdf(self, version_slug='latest'): return os.path.exists(self.get_pdf_path(version_slug)) def has_manpage(self, version_slug='latest'): return os.path.exists(self.get_manpage_path(version_slug)) def has_epub(self, version_slug='latest'): return os.path.exists(self.get_epub_path(version_slug)) def has_dash(self, version_slug='latest'): return os.path.exists(self.get_dash_path(version_slug)) def has_htmlzip(self, version_slug='latest'): return os.path.exists(self.get_htmlzip_path(version_slug)) @property def sponsored(self): return False def vcs_repo(self, version='latest'): #if hasattr(self, '_vcs_repo'): #return self._vcs_repo backend = backend_cls.get(self.repo_type) if not backend: repo = None else: proj = VCSProject(self.name, self.default_branch, self.checkout_path(version), self.repo) repo = backend(proj, version) #self._vcs_repo = repo return repo @property def contribution_backend(self): if hasattr(self, '_contribution_backend'): return self._contribution_backend if not self.vcs_repo: cb = None else: cb = self.vcs_repo.get_contribution_backend() self._contribution_backend = cb return cb def repo_lock(self, timeout=5, polling_interval=0.2): return Lock(self, timeout, polling_interval) def find(self, file, version): """ A balla API to find files inside of a projects dir. """ matches = [] for root, dirnames, filenames in os.walk(self.full_doc_path(version)): for filename in fnmatch.filter(filenames, file): matches.append(os.path.join(root, filename)) return matches def full_find(self, file, version): """ A balla API to find files inside of a projects dir. """ matches = [] for root, dirnames, filenames in os.walk(self.checkout_path(version)): for filename in fnmatch.filter(filenames, file): matches.append(os.path.join(root, filename)) return matches def get_latest_build(self): try: return self.builds.filter(type='html')[0] except IndexError: return None def api_versions(self): ret = [] for version_data in api.version.get(project=self.pk, active=True)['objects']: version = make_api_version(version_data) ret.append(version) return sort_version_aware(ret) def active_versions(self): return (self.versions.filter(built=True, active=True) | self.versions.filter(active=True, uploaded=True)) def ordered_active_versions(self): return sort_version_aware(self.versions.filter(active=True)) def all_active_versions(self): """A temporary workaround for active_versions filtering out things that were active, but failed to build """ return self.versions.filter(active=True) def version_from_branch_name(self, branch): try: return (self.versions.filter(identifier=branch) | self.versions.filter(identifier=('remotes/origin/%s' % branch)))[0] except IndexError: return None def get_default_version(self): """ Get the default version (slug). Returns self.default_version if the version with that slug actually exists (is built and published). Otherwise returns 'latest'. """ # latest is a special case where we don't have to check if it exists if self.default_version == 'latest': return self.default_version # check if the default_version exists version_qs = self.versions.filter( slug=self.default_version, active=True ) if version_qs.exists(): return self.default_version return 'latest' def get_default_branch(self): """ Get the version representing "latest" """ if self.default_branch: return self.default_branch else: return self.vcs_repo().fallback_branch def add_subproject(self, child): subproject, created = ProjectRelationship.objects.get_or_create( parent=self, child=child, ) return subproject def remove_subproject(self, child): ProjectRelationship.objects.filter(parent=self, child=child).delete() return
class IPAddress(ChangeLoggedModel, CustomFieldModel): """ An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like Prefixes, IPAddresses can optionally be assigned to a VRF. An IPAddress can optionally be assigned to an Interface. Interfaces can have zero or more IPAddresses assigned to them. An IPAddress can also optionally point to a NAT inside IP, designating itself as a NAT outside IP. This is useful, for example, when mapping public addresses to private addresses. When an Interface has been assigned an IPAddress which has a NAT outside IP, that Interface's Device can use either the inside or outside IP as its primary IP. """ family = models.PositiveSmallIntegerField(choices=AF_CHOICES, editable=False) address = IPAddressField(help_text='IPv4 or IPv6 address (with mask)') vrf = models.ForeignKey(to='ipam.VRF', on_delete=models.PROTECT, related_name='ip_addresses', blank=True, null=True, verbose_name='VRF') tenant = models.ForeignKey(to='tenancy.Tenant', on_delete=models.PROTECT, related_name='ip_addresses', blank=True, null=True) status = models.PositiveSmallIntegerField( choices=IPADDRESS_STATUS_CHOICES, default=IPADDRESS_STATUS_ACTIVE, verbose_name='Status', help_text='The operational status of this IP') role = models.PositiveSmallIntegerField( verbose_name='Role', choices=IPADDRESS_ROLE_CHOICES, blank=True, null=True, help_text='The functional role of this IP') interface = models.ForeignKey(to='dcim.Interface', on_delete=models.CASCADE, related_name='ip_addresses', blank=True, null=True) nat_inside = models.OneToOneField( to='self', on_delete=models.SET_NULL, related_name='nat_outside', blank=True, null=True, verbose_name='NAT (Inside)', help_text='The IP for which this address is the "outside" IP') description = models.CharField(max_length=100, blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') objects = IPAddressManager() tags = TaggableManager() csv_headers = [ 'address', 'vrf', 'tenant', 'status', 'role', 'device', 'virtual_machine', 'interface_name', 'is_primary', 'description', ] class Meta: ordering = ['family', 'address'] verbose_name = 'IP address' verbose_name_plural = 'IP addresses' def __str__(self): return str(self.address) def get_absolute_url(self): return reverse('ipam:ipaddress', args=[self.pk]) def get_duplicates(self): return IPAddress.objects.filter( vrf=self.vrf, address__net_host=str(self.address.ip)).exclude(pk=self.pk) def clean(self): if self.address: # Enforce unique IP space (if applicable) if self.role not in IPADDRESS_ROLES_NONUNIQUE and ( (self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE) or (self.vrf and self.vrf.enforce_unique)): duplicate_ips = self.get_duplicates() if duplicate_ips: raise ValidationError({ 'address': "Duplicate IP address found in {}: {}".format( "VRF {}".format(self.vrf) if self.vrf else "global table", duplicate_ips.first(), ) }) def save(self, *args, **kwargs): if self.address: # Infer address family from IPAddress object self.family = self.address.version super(IPAddress, self).save(*args, **kwargs) def to_csv(self): # Determine if this IP is primary for a Device if self.family == 4 and getattr(self, 'primary_ip4_for', False): is_primary = True elif self.family == 6 and getattr(self, 'primary_ip6_for', False): is_primary = True else: is_primary = False return ( self.address, self.vrf.rd if self.vrf else None, self.tenant.name if self.tenant else None, self.get_status_display(), self.get_role_display(), self.device.identifier if self.device else None, self.virtual_machine.name if self.virtual_machine else None, self.interface.name if self.interface else None, is_primary, self.description, ) @property def device(self): if self.interface: return self.interface.device return None @property def virtual_machine(self): if self.interface: return self.interface.virtual_machine return None def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] def get_role_class(self): return ROLE_CHOICE_CLASSES[self.role]
class VLAN(ChangeLoggedModel, CustomFieldModel): """ A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094). Each VLAN must be assigned to a Site, however VLAN IDs need not be unique within a Site. A VLAN may optionally be assigned to a VLANGroup, within which all VLAN IDs and names but be unique. Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero or more Prefixes assigned to it. """ site = models.ForeignKey(to='dcim.Site', on_delete=models.PROTECT, related_name='vlans', blank=True, null=True) group = models.ForeignKey(to='ipam.VLANGroup', on_delete=models.PROTECT, related_name='vlans', blank=True, null=True) vid = models.PositiveSmallIntegerField( verbose_name='ID', validators=[MinValueValidator(1), MaxValueValidator(4094)]) name = models.CharField(max_length=64) tenant = models.ForeignKey(to='tenancy.Tenant', on_delete=models.PROTECT, related_name='vlans', blank=True, null=True) status = models.PositiveSmallIntegerField(choices=VLAN_STATUS_CHOICES, default=1, verbose_name='Status') role = models.ForeignKey(to='ipam.Role', on_delete=models.SET_NULL, related_name='vlans', blank=True, null=True) description = models.CharField(max_length=100, blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') tags = TaggableManager() csv_headers = [ 'site', 'group_name', 'vid', 'name', 'tenant', 'status', 'role', 'description' ] class Meta: ordering = ['site', 'group', 'vid'] unique_together = [ ['group', 'vid'], ['group', 'name'], ] verbose_name = 'VLAN' verbose_name_plural = 'VLANs' def __str__(self): return self.display_name or super(VLAN, self).__str__() def get_absolute_url(self): return reverse('ipam:vlan', args=[self.pk]) def clean(self): # Validate VLAN group if self.group and self.group.site != self.site: raise ValidationError({ 'group': "VLAN group must belong to the assigned site ({}).".format( self.site) }) def to_csv(self): return ( self.site.name if self.site else None, self.group.name if self.group else None, self.vid, self.name, self.tenant.name if self.tenant else None, self.get_status_display(), self.role.name if self.role else None, self.description, ) @property def display_name(self): if self.vid and self.name: return "{} ({})".format(self.vid, self.name) return None def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] def get_members(self): # Return all interfaces assigned to this VLAN return Interface.objects.filter( Q(untagged_vlan_id=self.pk) | Q(tagged_vlans=self.pk))
class Prefix(ChangeLoggedModel, CustomFieldModel): """ A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be assigned to Sites and VRFs. A Prefix must be assigned a status and may optionally be assigned a used-define Role. A Prefix can also be assigned to a VLAN where appropriate. """ family = models.PositiveSmallIntegerField(choices=AF_CHOICES, editable=False) prefix = IPNetworkField(help_text='IPv4 or IPv6 network with mask') site = models.ForeignKey(to='dcim.Site', on_delete=models.PROTECT, related_name='prefixes', blank=True, null=True) vrf = models.ForeignKey(to='ipam.VRF', on_delete=models.PROTECT, related_name='prefixes', blank=True, null=True, verbose_name='VRF') tenant = models.ForeignKey(to='tenancy.Tenant', on_delete=models.PROTECT, related_name='prefixes', blank=True, null=True) vlan = models.ForeignKey(to='ipam.VLAN', on_delete=models.PROTECT, related_name='prefixes', blank=True, null=True, verbose_name='VLAN') status = models.PositiveSmallIntegerField( choices=PREFIX_STATUS_CHOICES, default=PREFIX_STATUS_ACTIVE, verbose_name='Status', help_text='Operational status of this prefix') role = models.ForeignKey(to='ipam.Role', on_delete=models.SET_NULL, related_name='prefixes', blank=True, null=True, help_text='The primary function of this prefix') is_pool = models.BooleanField( verbose_name='Is a pool', default=False, help_text='All IP addresses within this prefix are considered usable') description = models.CharField(max_length=100, blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') objects = PrefixQuerySet.as_manager() tags = TaggableManager() csv_headers = [ 'prefix', 'vrf', 'tenant', 'site', 'vlan_group', 'vlan_vid', 'status', 'role', 'is_pool', 'description', ] class Meta: ordering = ['vrf', 'family', 'prefix'] verbose_name_plural = 'prefixes' def __str__(self): return str(self.prefix) def get_absolute_url(self): return reverse('ipam:prefix', args=[self.pk]) def clean(self): if self.prefix: # Disallow host masks if self.prefix.version == 4 and self.prefix.prefixlen == 32: raise ValidationError({ 'prefix': "Cannot create host addresses (/32) as prefixes. Create an IPv4 address instead." }) elif self.prefix.version == 6 and self.prefix.prefixlen == 128: raise ValidationError({ 'prefix': "Cannot create host addresses (/128) as prefixes. Create an IPv6 address instead." }) # Enforce unique IP space (if applicable) if (self.vrf is None and settings.ENFORCE_GLOBAL_UNIQUE) or ( self.vrf and self.vrf.enforce_unique): duplicate_prefixes = self.get_duplicates() if duplicate_prefixes: raise ValidationError({ 'prefix': "Duplicate prefix found in {}: {}".format( "VRF {}".format(self.vrf) if self.vrf else "global table", duplicate_prefixes.first(), ) }) def save(self, *args, **kwargs): if self.prefix: # Clear host bits from prefix self.prefix = self.prefix.cidr # Infer address family from IPNetwork object self.family = self.prefix.version super(Prefix, self).save(*args, **kwargs) def to_csv(self): return ( self.prefix, self.vrf.rd if self.vrf else None, self.tenant.name if self.tenant else None, self.site.name if self.site else None, self.vlan.group.name if self.vlan and self.vlan.group else None, self.vlan.vid if self.vlan else None, self.get_status_display(), self.role.name if self.role else None, self.is_pool, self.description, ) def get_status_class(self): return STATUS_CHOICE_CLASSES[self.status] def get_duplicates(self): return Prefix.objects.filter(vrf=self.vrf, prefix=str( self.prefix)).exclude(pk=self.pk) def get_child_prefixes(self): """ Return all Prefixes within this Prefix and VRF. If this Prefix is a container in the global table, return child Prefixes belonging to any VRF. """ if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER: return Prefix.objects.filter( prefix__net_contained=str(self.prefix)) else: return Prefix.objects.filter(prefix__net_contained=str( self.prefix), vrf=self.vrf) def get_child_ips(self): """ Return all IPAddresses within this Prefix and VRF. If this Prefix is a container in the global table, return child IPAddresses belonging to any VRF. """ if self.vrf is None and self.status == PREFIX_STATUS_CONTAINER: return IPAddress.objects.filter( address__net_host_contained=str(self.prefix)) else: return IPAddress.objects.filter(address__net_host_contained=str( self.prefix), vrf=self.vrf) def get_available_prefixes(self): """ Return all available Prefixes within this prefix as an IPSet. """ prefix = netaddr.IPSet(self.prefix) child_prefixes = netaddr.IPSet( [child.prefix for child in self.get_child_prefixes()]) available_prefixes = prefix - child_prefixes return available_prefixes def get_available_ips(self): """ Return all available IPs within this prefix as an IPSet. """ prefix = netaddr.IPSet(self.prefix) child_ips = netaddr.IPSet( [ip.address.ip for ip in self.get_child_ips()]) available_ips = prefix - child_ips # Remove unusable IPs from non-pool prefixes if not self.is_pool: available_ips -= netaddr.IPSet([ netaddr.IPAddress(self.prefix.first), netaddr.IPAddress(self.prefix.last), ]) return available_ips def get_first_available_prefix(self): """ Return the first available child prefix within the prefix (or None). """ available_prefixes = self.get_available_prefixes() if not available_prefixes: return None return available_prefixes.iter_cidrs()[0] def get_first_available_ip(self): """ Return the first available IP within the prefix (or None). """ available_ips = self.get_available_ips() if not available_ips: return None return '{}/{}'.format(next(available_ips.__iter__()), self.prefix.prefixlen) def get_utilization(self): """ Determine the utilization of the prefix and return it as a percentage. For Prefixes with a status of "container", calculate utilization based on child prefixes. For all others, count child IP addresses. """ if self.status == PREFIX_STATUS_CONTAINER: queryset = Prefix.objects.filter(prefix__net_contained=str( self.prefix), vrf=self.vrf) child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) return int(float(child_prefixes.size) / self.prefix.size * 100) else: # Compile an IPSet to avoid counting duplicate IPs child_count = netaddr.IPSet( [ip.address.ip for ip in self.get_child_ips()]).size prefix_size = self.prefix.size if self.family == 4 and self.prefix.prefixlen < 31 and not self.is_pool: prefix_size -= 2 return int(float(child_count) / prefix_size * 100)
class Aggregate(ChangeLoggedModel, CustomFieldModel): """ An aggregate exists at the root level of the IP address space hierarchy in NetBox. Aggregates are used to organize the hierarchy and track the overall utilization of available address space. Each Aggregate is assigned to a RIR. """ family = models.PositiveSmallIntegerField(choices=AF_CHOICES) prefix = IPNetworkField() rir = models.ForeignKey(to='ipam.RIR', on_delete=models.PROTECT, related_name='aggregates', verbose_name='RIR') date_added = models.DateField(blank=True, null=True) description = models.CharField(max_length=100, blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') tags = TaggableManager() csv_headers = ['prefix', 'rir', 'date_added', 'description'] class Meta: ordering = ['family', 'prefix'] def __str__(self): return str(self.prefix) def get_absolute_url(self): return reverse('ipam:aggregate', args=[self.pk]) def clean(self): if self.prefix: # Clear host bits from prefix self.prefix = self.prefix.cidr # Ensure that the aggregate being added is not covered by an existing aggregate covering_aggregates = Aggregate.objects.filter( prefix__net_contains_or_equals=str(self.prefix)) if self.pk: covering_aggregates = covering_aggregates.exclude(pk=self.pk) if covering_aggregates: raise ValidationError({ 'prefix': "Aggregates cannot overlap. {} is already covered by an existing aggregate ({})." .format(self.prefix, covering_aggregates[0]) }) # Ensure that the aggregate being added does not cover an existing aggregate covered_aggregates = Aggregate.objects.filter( prefix__net_contained=str(self.prefix)) if self.pk: covered_aggregates = covered_aggregates.exclude(pk=self.pk) if covered_aggregates: raise ValidationError({ 'prefix': "Aggregates cannot overlap. {} covers an existing aggregate ({})." .format(self.prefix, covered_aggregates[0]) }) def save(self, *args, **kwargs): if self.prefix: # Infer address family from IPNetwork object self.family = self.prefix.version super(Aggregate, self).save(*args, **kwargs) def to_csv(self): return ( self.prefix, self.rir.name, self.date_added, self.description, ) def get_utilization(self): """ Determine the prefix utilization of the aggregate and return it as a percentage. """ queryset = Prefix.objects.filter( prefix__net_contained_or_equal=str(self.prefix)) child_prefixes = netaddr.IPSet([p.prefix for p in queryset]) return int(float(child_prefixes.size) / self.prefix.size * 100)
class Article(CachingMixin, models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) is_live = models.BooleanField('Display on site', default=True) show_in_lists = models.BooleanField('Show in lists', default=True) allow_comments = models.BooleanField('Allow comments', default=True) title = models.CharField(max_length=128) slug = models.SlugField(unique=True) pubdate = models.DateTimeField(default=datetime.now) subhead = models.CharField(max_length=128) authors = models.ManyToManyField(Person, blank=True, related_name='article_authors') image = ImageField( upload_to='img/uploads/article_images', help_text='Resized to fit 100% column width in template', blank=True, null=True) image_caption = models.TextField(blank=True) image_credit = models.CharField( max_length=128, blank=True, help_text= 'Optional. Will be appended to end of caption in parens. Accepts HTML.' ) body = models.TextField() summary = models.TextField() article_type = models.CharField(max_length=32, blank=True) category = models.ForeignKey('Category', on_delete=models.CASCADE) people = models.ManyToManyField(Person, blank=True) organizations = models.ManyToManyField(Organization, blank=True) code = models.ManyToManyField(Code, blank=True) tags = TaggableManager( blank=True, help_text= 'Automatic combined list of Technology Tags and Concept Tags, for easy searching' ) technology_tags = TaggableManager( verbose_name='Technology Tags', help_text= 'A comma-separated list of tags describing relevant technologies', through=TechnologyTaggedItem, blank=True) concept_tags = TaggableManager( verbose_name='Concept Tags', help_text='A comma-separated list of tags describing relevant concepts', through=ConceptTaggedItem, blank=True) objects = models.Manager() live_objects = LiveArticleManager() disable_auto_linebreaks = models.BooleanField( default=False, help_text= 'Check this if body and article blocks already have HTML paragraph tags.' ) article_js_header = models.TextField(blank=True) article_js_footer = models.TextField(blank=True) class Meta: ordering = ( '-pubdate', 'title', ) def __str__(self): return '%s' % self.title @models.permalink def get_absolute_url(self): return ('article_detail', (), { 'section': self.section.slug, 'slug': self.slug }) @property def section(self): '''follow article category through to section''' if self.category: return self.category.section return None @property def pretty_pubdate(self): '''pre-process for simpler template logic''' return dj_date(self.pubdate, "F j, Y") @property def pretty_caption(self): '''pre-process for simpler template logic''' _caption = self.image_caption or '' _credit = self.image_credit if _credit: _caption = '%s (%s)' % (_caption, _credit) return _caption @property def pretty_body_text(self): '''pre-process for simpler template logic''' _body = self.body if not self.disable_auto_linebreaks: # allow admin users to provide text # that already contains <p> tags _body = linebreaks(_body) return _body @property def safe_summary(self): '''suitable for use in places that must avoid nested anchor tags''' return removetags(self.summary, 'a') @property def merged_tag_list(self): '''return a combined list of technology_tags and concept_tags''' return [ item for item in itertools.chain(self.technology_tags.all(), self.concept_tags.all()) ] def get_live_organization_set(self): return self.organizations.filter(is_live=True) def get_live_person_set(self): return self.people.filter(is_live=True) def get_live_author_set(self): return self.authors.filter(is_live=True) def get_live_code_set(self): return self.code.filter(is_live=True) def get_live_guide_set(self): return self.guidearticle_set.filter(guide__is_live=True, guide__show_in_lists=True, guide__pubdate__lte=datetime.now()) def get_live_author_bio_set(self): # only authors with acutal bio information author_set = self.get_live_author_set().exclude(description='') # filter out bio boxes for Erin, Erika, authors_to_exclude = ['erin-kissane', 'erika-owens', 'kio-stark'] author_set = author_set.exclude(slug__in=authors_to_exclude) return author_set
class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): """ A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique. Each Device must be assigned to a site, and optionally to a rack within that site. Associating a device with a particular rack face or unit is optional (for example, vertically mounted PDUs do not consume rack units). When a new Device is created, console/power/interface/device bay components are created along with it as dictated by the component templates assigned to its DeviceType. Components can also be added, modified, or deleted after the creation of a Device. """ device_type = models.ForeignKey(to='dcim.DeviceType', on_delete=models.PROTECT, related_name='instances') device_role = models.ForeignKey(to='dcim.DeviceRole', on_delete=models.PROTECT, related_name='devices') tenant = models.ForeignKey(to='tenancy.Tenant', on_delete=models.PROTECT, related_name='devices', blank=True, null=True) platform = models.ForeignKey(to='dcim.Platform', on_delete=models.SET_NULL, related_name='devices', blank=True, null=True) name = models.CharField(max_length=64, blank=True, null=True) _name = NaturalOrderingField(target_field='name', max_length=100, blank=True, null=True) serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number') asset_tag = models.CharField( max_length=50, blank=True, null=True, unique=True, verbose_name='Asset tag', help_text='A unique tag used to identify this device') site = models.ForeignKey(to='dcim.Site', on_delete=models.PROTECT, related_name='devices') rack = models.ForeignKey(to='dcim.Rack', on_delete=models.PROTECT, related_name='devices', blank=True, null=True) position = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], verbose_name='Position (U)', help_text='The lowest-numbered unit occupied by the device') face = models.CharField(max_length=50, blank=True, choices=DeviceFaceChoices, verbose_name='Rack face') status = models.CharField(max_length=50, choices=DeviceStatusChoices, default=DeviceStatusChoices.STATUS_ACTIVE) primary_ip4 = models.OneToOneField(to='ipam.IPAddress', on_delete=models.SET_NULL, related_name='primary_ip4_for', blank=True, null=True, verbose_name='Primary IPv4') primary_ip6 = models.OneToOneField(to='ipam.IPAddress', on_delete=models.SET_NULL, related_name='primary_ip6_for', blank=True, null=True, verbose_name='Primary IPv6') cluster = models.ForeignKey(to='virtualization.Cluster', on_delete=models.SET_NULL, related_name='devices', blank=True, null=True) virtual_chassis = models.ForeignKey(to='VirtualChassis', on_delete=models.SET_NULL, related_name='members', blank=True, null=True) vc_position = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MaxValueValidator(255)]) vc_priority = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MaxValueValidator(255)]) comments = models.TextField(blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') images = GenericRelation(to='extras.ImageAttachment') tags = TaggableManager(through=TaggedItem) objects = RestrictedQuerySet.as_manager() csv_headers = [ 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', 'site', 'rack_group', 'rack_name', 'position', 'face', 'comments', ] clone_fields = [ 'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster', ] STATUS_CLASS_MAP = { DeviceStatusChoices.STATUS_OFFLINE: 'warning', DeviceStatusChoices.STATUS_ACTIVE: 'success', DeviceStatusChoices.STATUS_PLANNED: 'info', DeviceStatusChoices.STATUS_STAGED: 'primary', DeviceStatusChoices.STATUS_FAILED: 'danger', DeviceStatusChoices.STATUS_INVENTORY: 'default', DeviceStatusChoices.STATUS_DECOMMISSIONING: 'warning', } class Meta: ordering = ('_name', 'pk') # Name may be null unique_together = ( ('site', 'tenant', 'name'), # See validate_unique below ('rack', 'position', 'face'), ('virtual_chassis', 'vc_position'), ) def __str__(self): return self.display_name or super().__str__() def get_absolute_url(self): return reverse('dcim:device', args=[self.pk]) def validate_unique(self, exclude=None): # Check for a duplicate name on a device assigned to the same Site and no Tenant. This is necessary # because Django does not consider two NULL fields to be equal, and thus will not trigger a violation # of the uniqueness constraint without manual intervention. if self.name and self.tenant is None: if Device.objects.exclude(pk=self.pk).filter(name=self.name, site=self.site, tenant__isnull=True): raise ValidationError( {'name': 'A device with this name already exists.'}) super().validate_unique(exclude) def clean(self): super().clean() # Validate site/rack combination if self.rack and self.site != self.rack.site: raise ValidationError({ 'rack': "Rack {} does not belong to site {}.".format( self.rack, self.site), }) if self.rack is None: if self.face: raise ValidationError({ 'face': "Cannot select a rack face without assigning a rack.", }) if self.position: raise ValidationError({ 'face': "Cannot select a rack position without assigning a rack.", }) # Validate position/face combination if self.position and not self.face: raise ValidationError({ 'face': "Must specify rack face when defining rack position.", }) # Prevent 0U devices from being assigned to a specific position if self.position and self.device_type.u_height == 0: raise ValidationError({ 'position': "A U0 device type ({}) cannot be assigned to a rack position.". format(self.device_type) }) if self.rack: try: # Child devices cannot be assigned to a rack face/unit if self.device_type.is_child_device and self.face: raise ValidationError({ 'face': "Child device types cannot be assigned to a rack face. This is an attribute of the " "parent device." }) if self.device_type.is_child_device and self.position: raise ValidationError({ 'position': "Child device types cannot be assigned to a rack position. This is an attribute of " "the parent device." }) # Validate rack space rack_face = self.face if not self.device_type.is_full_depth else None exclude_list = [self.pk] if self.pk else [] available_units = self.rack.get_available_units( u_height=self.device_type.u_height, rack_face=rack_face, exclude=exclude_list) if self.position and self.position not in available_units: raise ValidationError({ 'position': "U{} is already occupied or does not have sufficient space to accommodate a(n) " "{} ({}U).".format(self.position, self.device_type, self.device_type.u_height) }) except DeviceType.DoesNotExist: pass # Validate primary IP addresses vc_interfaces = self.vc_interfaces.all() if self.primary_ip4: if self.primary_ip4.family != 4: raise ValidationError({ 'primary_ip4': f"{self.primary_ip4} is not an IPv4 address." }) if self.primary_ip4.assigned_object in vc_interfaces: pass elif self.primary_ip4.nat_inside is not None and self.primary_ip4.nat_inside.assigned_object in vc_interfaces: pass else: raise ValidationError({ 'primary_ip4': f"The specified IP address ({self.primary_ip4}) is not assigned to this device." }) if self.primary_ip6: if self.primary_ip6.family != 6: raise ValidationError({ 'primary_ip6': f"{self.primary_ip6} is not an IPv6 address." }) if self.primary_ip6.assigned_object in vc_interfaces: pass elif self.primary_ip6.nat_inside is not None and self.primary_ip6.nat_inside.assigned_object in vc_interfaces: pass else: raise ValidationError({ 'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device." }) # Validate manufacturer/platform if hasattr(self, 'device_type') and self.platform: if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer: raise ValidationError({ 'platform': "The assigned platform is limited to {} device types, but this device's type belongs " "to {}.".format(self.platform.manufacturer, self.device_type.manufacturer) }) # A Device can only be assigned to a Cluster in the same Site (or no Site) if self.cluster and self.cluster.site is not None and self.cluster.site != self.site: raise ValidationError({ 'cluster': "The assigned cluster belongs to a different site ({})".format( self.cluster.site) }) # Validate virtual chassis assignment if self.virtual_chassis and self.vc_position is None: raise ValidationError({ 'vc_position': "A device assigned to a virtual chassis must have its position defined." }) def save(self, *args, **kwargs): is_new = not bool(self.pk) super().save(*args, **kwargs) # If this is a new Device, instantiate all of the related components per the DeviceType definition if is_new: ConsolePort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.consoleporttemplates.all() ]) ConsoleServerPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.consoleserverporttemplates.all() ]) PowerPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.powerporttemplates.all() ]) PowerOutlet.objects.bulk_create([ x.instantiate(self) for x in self.device_type.poweroutlettemplates.all() ]) Interface.objects.bulk_create([ x.instantiate(self) for x in self.device_type.interfacetemplates.all() ]) RearPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.rearporttemplates.all() ]) FrontPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.frontporttemplates.all() ]) DeviceBay.objects.bulk_create([ x.instantiate(self) for x in self.device_type.devicebaytemplates.all() ]) # Update Site and Rack assignment for any child Devices devices = Device.objects.filter(parent_bay__device=self) for device in devices: device.site = self.site device.rack = self.rack device.save() def to_csv(self): return ( self.name or '', self.device_role.name, self.tenant.name if self.tenant else None, self.device_type.manufacturer.name, self.device_type.model, self.platform.name if self.platform else None, self.serial, self.asset_tag, self.get_status_display(), self.site.name, self.rack.group.name if self.rack and self.rack.group else None, self.rack.name if self.rack else None, self.position, self.get_face_display(), self.comments, ) @property def display_name(self): if self.name: return self.name elif self.virtual_chassis: return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' elif self.device_type: return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' else: return '' # Device has not yet been created @property def identifier(self): """ Return the device name if set; otherwise return the Device's primary key as {pk} """ if self.name is not None: return self.name return '{{{}}}'.format(self.pk) @property def primary_ip(self): if settings.PREFER_IPV4 and self.primary_ip4: return self.primary_ip4 elif self.primary_ip6: return self.primary_ip6 elif self.primary_ip4: return self.primary_ip4 else: return None def get_vc_master(self): """ If this Device is a VirtualChassis member, return the VC master. Otherwise, return None. """ return self.virtual_chassis.master if self.virtual_chassis else None @property def vc_interfaces(self): """ Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another Device belonging to the same VirtualChassis. """ filter = Q(device=self) if self.virtual_chassis and self.virtual_chassis.master == self: filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False) return Interface.objects.filter(filter) def get_cables(self, pk_list=False): """ Return a QuerySet or PK list matching all Cables connected to a component of this Device. """ cable_pks = [] for component_model in [ ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, FrontPort, RearPort ]: cable_pks += component_model.objects.filter( device=self, cable__isnull=False).values_list('cable', flat=True) if pk_list: return cable_pks return Cable.objects.filter(pk__in=cable_pks) def get_children(self): """ Return the set of child Devices installed in DeviceBays within this Device. """ return Device.objects.filter(parent_bay__device=self.pk) def get_status_class(self): return self.STATUS_CLASS_MAP.get(self.status)
class Dataset(StatusModel, TimeStampedModel): """dataset container class A `Dataset` represents a set of ``Datafile`s that belong together. This is mostly a container for `Datafile`s. """ # meta class Meta: app_label = "djspikeval" get_latest_by = "modified" ordering = ("-modified", "name") # choices STATUS = AccessChoices # fields name = models.CharField( _("name"), max_length=255, help_text="The name will be used as an identifier for the Dataset. " "(character limit: 255)") description = models.TextField( blank=True, help_text="Use this field to give a detailed description of the " "Dataset. Although there is no limit to the content " "of this field, you may want to provide an attached file " "if your space or editing requirements are not met. " "(character limit: none)") parameter = models.CharField( max_length=255, default="No.", help_text= "Individual datafiles of the dataset can have a parameter attached " "that can be used to order and distinguish the datafiles. This may " "be a simulation or experimental parameter that has been varied " "systematically or just a numbering (default). " "(character limit: 255)") user = models.ForeignKey( settings.AUTH_USER_MODEL, default=2, help_text="The user who contributed this dataset.") # managers kind = TaggableManager( _("Dataset Kind"), help_text="A comma-separated list of tags classifying the Dataset.", blank=True) asset_set = GenericRelation("base.Asset") @property def attachment_set(self): return self.asset_set.filter(kind="attachment") @property def module_enabled_set(self): return self.module_set.filter(enabled=True) # methods def __unicode__(self): return unicode("Dataset: {}".format(self.name)) @models.permalink def get_absolute_url(self): return "dataset:detail", (self.pk, ), {} @models.permalink def get_delete_url(self): return "dataset:delete", (self.pk, ), {} def is_public(self): return self.status == Dataset.STATUS.public def is_editable(self, user): return self.user == user or getattr(user, "is_superuser", False) is True def is_accessible(self, user): return self.is_public() or self.is_editable(user) def toggle(self): if self.status == Dataset.STATUS.public: self.status = Dataset.STATUS.private else: self.status = Dataset.STATUS.public self.save() def datafile_set_valid(self): return self.datafile_set.filter( ~models.Q(valid_rd_log__contains="ERROR"), ~models.Q(valid_gt_log__contains="ERROR")) def submission_count(self, user=None): try: if user.is_superuser(): return self.submission_set.count() return self.submission_set.filter( models.Q(status__exact="public") | models.Q(owner_id=user.pk)).count() except: return self.submission_set.filter(status__exact="public").count()
class DeviceType(ChangeLoggedModel, CustomFieldModel): """ A DeviceType represents a particular make (Manufacturer) and model of device. It specifies rack height and depth, as well as high-level functional role(s). Each DeviceType can have an arbitrary number of component templates assigned to it, which define console, power, and interface objects. For example, a Juniper EX4300-48T DeviceType would have: * 1 ConsolePortTemplate * 2 PowerPortTemplates * 48 InterfaceTemplates When a new Device of this type is created, the appropriate console, power, and interface objects (as defined by the DeviceType) are automatically created as well. """ manufacturer = models.ForeignKey(to='dcim.Manufacturer', on_delete=models.PROTECT, related_name='device_types') model = models.CharField(max_length=50) slug = models.SlugField() part_number = models.CharField(max_length=50, blank=True, help_text='Discrete part number (optional)') u_height = models.PositiveSmallIntegerField(default=1, verbose_name='Height (U)') is_full_depth = models.BooleanField( default=True, verbose_name='Is full depth', help_text='Device consumes both front and rear rack faces') subdevice_role = models.CharField( max_length=50, choices=SubdeviceRoleChoices, blank=True, verbose_name='Parent/child status', help_text= 'Parent devices house child devices in device bays. Leave blank ' 'if this device type is neither a parent nor a child.') front_image = models.ImageField(upload_to='devicetype-images', blank=True) rear_image = models.ImageField(upload_to='devicetype-images', blank=True) comments = models.TextField(blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') tags = TaggableManager(through=TaggedItem) objects = RestrictedQuerySet.as_manager() clone_fields = [ 'manufacturer', 'u_height', 'is_full_depth', 'subdevice_role', ] class Meta: ordering = ['manufacturer', 'model'] unique_together = [ ['manufacturer', 'model'], ['manufacturer', 'slug'], ] def __str__(self): return self.model def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Save a copy of u_height for validation in clean() self._original_u_height = self.u_height # Save references to the original front/rear images self._original_front_image = self.front_image self._original_rear_image = self.rear_image def get_absolute_url(self): return reverse('dcim:devicetype', args=[self.pk]) def to_yaml(self): data = OrderedDict(( ('manufacturer', self.manufacturer.name), ('model', self.model), ('slug', self.slug), ('part_number', self.part_number), ('u_height', self.u_height), ('is_full_depth', self.is_full_depth), ('subdevice_role', self.subdevice_role), ('comments', self.comments), )) # Component templates if self.consoleporttemplates.exists(): data['console-ports'] = [{ 'name': c.name, 'type': c.type, } for c in self.consoleporttemplates.all()] if self.consoleserverporttemplates.exists(): data['console-server-ports'] = [{ 'name': c.name, 'type': c.type, } for c in self.consoleserverporttemplates.all()] if self.powerporttemplates.exists(): data['power-ports'] = [{ 'name': c.name, 'type': c.type, 'maximum_draw': c.maximum_draw, 'allocated_draw': c.allocated_draw, } for c in self.powerporttemplates.all()] if self.poweroutlettemplates.exists(): data['power-outlets'] = [{ 'name': c.name, 'type': c.type, 'power_port': c.power_port.name if c.power_port else None, 'feed_leg': c.feed_leg, } for c in self.poweroutlettemplates.all()] if self.interfacetemplates.exists(): data['interfaces'] = [{ 'name': c.name, 'type': c.type, 'mgmt_only': c.mgmt_only, } for c in self.interfacetemplates.all()] if self.frontporttemplates.exists(): data['front-ports'] = [{ 'name': c.name, 'type': c.type, 'rear_port': c.rear_port.name, 'rear_port_position': c.rear_port_position, } for c in self.frontporttemplates.all()] if self.rearporttemplates.exists(): data['rear-ports'] = [{ 'name': c.name, 'type': c.type, 'positions': c.positions, } for c in self.rearporttemplates.all()] if self.devicebaytemplates.exists(): data['device-bays'] = [{ 'name': c.name, } for c in self.devicebaytemplates.all()] return yaml.dump(dict(data), sort_keys=False) def clean(self): # If editing an existing DeviceType to have a larger u_height, first validate that *all* instances of it have # room to expand within their racks. This validation will impose a very high performance penalty when there are # many instances to check, but increasing the u_height of a DeviceType should be a very rare occurrence. if self.pk and self.u_height > self._original_u_height: for d in Device.objects.filter(device_type=self, position__isnull=False): face_required = None if self.is_full_depth else d.face u_available = d.rack.get_available_units( u_height=self.u_height, rack_face=face_required, exclude=[d.pk]) if d.position not in u_available: raise ValidationError({ 'u_height': "Device {} in rack {} does not have sufficient space to accommodate a height of " "{}U".format(d, d.rack, self.u_height) }) # If modifying the height of an existing DeviceType to 0U, check for any instances assigned to a rack position. elif self.pk and self._original_u_height > 0 and self.u_height == 0: racked_instance_count = Device.objects.filter( device_type=self, position__isnull=False).count() if racked_instance_count: url = f"{reverse('dcim:device_list')}?manufactuer_id={self.manufacturer_id}&device_type_id={self.pk}" raise ValidationError({ 'u_height': mark_safe( f'Unable to set 0U height: Found <a href="{url}">{racked_instance_count} instances</a> already ' f'mounted within racks.') }) if (self.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT ) and self.devicebaytemplates.count(): raise ValidationError({ 'subdevice_role': "Must delete all device bay templates associated with this device before " "declassifying it as a parent device." }) if self.u_height and self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD: raise ValidationError( {'u_height': "Child device types must be 0U."}) def save(self, *args, **kwargs): ret = super().save(*args, **kwargs) # Delete any previously uploaded image files that are no longer in use if self.front_image != self._original_front_image: self._original_front_image.delete(save=False) if self.rear_image != self._original_rear_image: self._original_rear_image.delete(save=False) return ret def delete(self, *args, **kwargs): super().delete(*args, **kwargs) # Delete any uploaded image files if self.front_image: self.front_image.delete(save=False) if self.rear_image: self.rear_image.delete(save=False) @property def display_name(self): return f'{self.manufacturer.name} {self.model}' @property def is_parent_device(self): return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT @property def is_child_device(self): return self.subdevice_role == SubdeviceRoleChoices.ROLE_CHILD
class PublicBody(models.Model): name = models.CharField(_("Name"), max_length=255) other_names = models.TextField(_("Other names"), default="", blank=True) slug = models.SlugField(_("Slug"), max_length=255) description = models.TextField(_("Description"), blank=True) url = models.URLField(_("URL"), null=True, blank=True, max_length=500) parent = models.ForeignKey('PublicBody', null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="children") root = models.ForeignKey('PublicBody', null=True, blank=True, default=None, on_delete=models.SET_NULL, related_name="descendants") depth = models.SmallIntegerField(default=0) classification = models.CharField(_("Classification"), max_length=255, blank=True) classification_slug = models.SlugField(_("Classification Slug"), max_length=255, blank=True) email = models.EmailField(_("Email"), null=True, blank=True) contact = models.TextField(_("Contact"), blank=True) address = models.TextField(_("Address"), blank=True) website_dump = models.TextField(_("Website Dump"), null=True, blank=True) request_note = models.TextField(_("request note"), blank=True) file_index = models.CharField(_("file index"), max_length=1024, blank=True) org_chart = models.CharField(_("organisational chart"), max_length=1024, blank=True) _created_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("Created by"), blank=True, null=True, related_name='public_body_creators', on_delete=models.SET_NULL, default=1) _updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("Updated by"), blank=True, null=True, related_name='public_body_updaters', on_delete=models.SET_NULL, default=1) created_at = models.DateTimeField(_("Created at"), default=timezone.now) updated_at = models.DateTimeField(_("Updated at"), default=timezone.now) confirmed = models.BooleanField(_("confirmed"), default=True) number_of_requests = models.IntegerField(_("Number of requests"), default=0) site = models.ForeignKey(Site, verbose_name=_("Site"), null=True, on_delete=models.SET_NULL, default=settings.SITE_ID) jurisdiction = models.ForeignKey(Jurisdiction, verbose_name=_('Jurisdiction'), blank=True, null=True, on_delete=models.SET_NULL) laws = models.ManyToManyField( FoiLaw, verbose_name=_("Freedom of Information Laws")) tags = TaggableManager(through=TaggedPublicBody, blank=True) non_filtered_objects = models.Manager() objects = PublicBodyManager() published = objects class Meta: ordering = ('name', ) verbose_name = _("Public Body") verbose_name_plural = _("Public Bodies") serializable_fields = ('name', 'slug', 'request_note_html', 'description', 'url', 'email', 'contact', 'address', 'domain') def __str__(self): return u"%s (%s)" % (self.name, self.jurisdiction) @property def created_by(self): return self._created_by @property def updated_by(self): return self._updated_by @property def domain(self): if self.url: return self.url.split("/")[2] return None @property def request_note_html(self): return markdown(self.request_note) @property def tag_list(self): return edit_string_for_tags(self.tags.all()) @property def default_law(self): return FoiLaw.get_default_law(self) def get_absolute_url(self): return reverse('publicbody-show', kwargs={"slug": self.slug}) def get_absolute_domain_url(self): return u"%s%s" % (settings.SITE_URL, self.get_absolute_url()) def get_label(self): return mark_safe( '%(name)s - <a href="%(url)s" class="target-new info-link">%(detail)s</a>' % { "name": escape(self.name), "url": self.get_absolute_url(), "detail": _("More Info") }) def confirm(self): if self.confirmed: return None self.confirmed = True self.save() counter = 0 for request in self.foirequest_set.all(): if request.confirmed_public_body(): counter += 1 return counter def as_json(self): d = {} for field in self.serializable_fields: d[field] = getattr(self, field) d['laws'] = [self.default_law.as_dict()] d['jurisdiction'] = self.jurisdiction.name return json.dumps(d) @property def children_count(self): return len(PublicBody.objects.filter(parent=self)) @classmethod def export_csv(cls, queryset): fields = ( "id", "name", "email", "contact", "address", "url", "classification", "jurisdiction__slug", "tags", "other_names", "website_dump", "description", "request_note", "parent__name", ) return export_csv(queryset, fields)
class Book(models.Model): name = models.CharField(max_length=50, verbose_name='عنوان') subject = models.CharField(max_length=100, verbose_name='موضوع') code = models.CharField(max_length=13, blank=False, unique=True, null=False, verbose_name='شابک') publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, verbose_name='ناشر') mainCat = models.ForeignKey(Categori, on_delete=models.SET_DEFAULT, default=1, related_name='main', verbose_name='نوع محصول') secondCat = models.ForeignKey(Categori, on_delete=models.SET_NULL, blank=True, null=True, related_name='second', verbose_name='دسته فرعی') thirdCat = models.ForeignKey(Categori, on_delete=models.SET_NULL, blank=True, null=True, related_name='third', verbose_name='دسته فرعی دوم') description = models.CharField(max_length=200, verbose_name='توضیحات', blank=True, null=True) writer = models.ForeignKey(Author, on_delete=models.CASCADE, verbose_name='نویسنده', blank=True, null=True) translator = models.ForeignKey(Translator, on_delete=models.CASCADE, verbose_name='مترجم', blank=True, null=True) image = models.ImageField(storage=fs, upload_to='Books', verbose_name='تصویر', default=defaultImagePathBook) #tamin konande ha Dakheli = models.CharField(max_length=20, verbose_name='داخلی', default='NULL', blank=True) Gostaresh = models.CharField(max_length=20, verbose_name='گسترش', default='NULL', blank=True) Ghoghnus = models.CharField(max_length=20, verbose_name='ققنوس', default='NULL', blank=True) Asar = models.CharField(max_length=20, verbose_name='اثار', default='NULL', blank=True) GitaMehr = models.CharField(max_length=20, verbose_name='گیتا مهر', default='NULL', blank=True) Elias = models.CharField(max_length=20, verbose_name='الیاس', default='NULL', blank=True) PezeshkiPasargad = models.CharField(max_length=20, verbose_name='پاسارگاد', default='NULL', blank=True) PaiamNur = models.CharField(max_length=20, verbose_name='پیام نور', default='NULL', blank=True) Jungle = models.CharField(max_length=20, verbose_name='جنگل', default='NULL', blank=True) Bidgol = models.CharField(max_length=20, verbose_name='بیدگل', default='NULL', blank=True) GotenBerg = models.CharField(max_length=20, verbose_name='گتنبرگ', default='NULL', blank=True) Kharazmi = models.CharField(max_length=20, verbose_name='خوارزمی', default='NULL', blank=True) SimaieDanesh = models.CharField(max_length=20, verbose_name='سیمای دانش', default='NULL', blank=True) #--------------------------- inventory = models.BooleanField(default=False, verbose_name='موجودی') price = models.DecimalField(decimal_places=0, max_digits=6, verbose_name='قیمت') printNumber = models.IntegerField(blank=True, null=True, verbose_name='نوبت چاپ') publishYear = models.CharField(max_length=4, blank=True, null=True, verbose_name='سال چاپ') created_time = models.DateField(editable=False) updated_time = models.DateField(editable=False) language = models.SmallIntegerField(default=0, choices=((a, b) for a, b in langs.items()), verbose_name='زبان کتاب') ghat = models.SmallIntegerField(default=0, choices=((a, b) for a, b in ghats.items()), verbose_name='قطع') jeld = models.SmallIntegerField(default=0, choices=((a, b) for a, b in jelds.items()), verbose_name='نوع جلد') pages = models.DecimalField(max_digits=4, decimal_places=0, verbose_name='تعداد صفحه', blank=True, null=True) weight = models.DecimalField(max_digits=5, decimal_places=0, verbose_name='وزن', null=True, blank=True) likes = models.IntegerField(default=0, verbose_name='پسندیدم ها') dilikes = models.IntegerField(default=0, verbose_name='نپسندیدم ها') views = models.DecimalField(default=0, max_digits=4, decimal_places=0, verbose_name='بازدید') discount = models.DecimalField(default=0, max_digits=3, decimal_places=0, verbose_name='تخفیف') sales = models.DecimalField(default=0, max_digits=4, decimal_places=0, verbose_name='تعداد فروش', editable=False) available = models.BooleanField(default=True, verbose_name='قابل خرید') visiblity = models.BooleanField(default=True, verbose_name='نمایش') stock = models.DecimalField(default=4, max_digits=2, decimal_places=0, verbose_name='قابل خرید') tags = TaggableManager() objects = BookManager() def __str__(self): return self.name def save(self, *args, **kwargs): if not self.id: self.created_time = timezone.now() self.updated_time = timezone.now() return super(Book, self).save(*args, **kwargs) def getTaminKonande(self): all = { 'Dakheli': self.Dakheli, 'Gostaresh': self.Gostaresh, 'Ghoghuns': self.Ghoghnus, 'Asar': self.Asar, 'Bidgol': self.Bidgol, 'GotenBerg': self.GotenBerg, 'Elias': self.Elias, 'Kharazmi': self.Kharazmi, 'Jungle': self.Jungle, 'PaiamNur': self.PaiamNur, 'PezeshkiPasargad': self.PezeshkiPasargad, 'SimaieDanesh': self.SimaieDanesh } taminkonande = '' max = -1 if self.inventory == True: for key in all.keys(): if all[key] != 'NULL': newMax = taminKondandeOlaviat[key] if max < newMax: max = newMax taminkonande = key if max != -1: return self.__dict__[taminkonande] else: return 'NULL' def getRealPrice(self): return int(self.price) def calculatePriceWithDiscount(self): #book price with discount if self.discount > 0: dis = (int(self.discount) / 100) * int(self.price) return {'stat': True, 'price': int(int(self.price) - dis)} return {'stat': False, 'price': self.price} def getBookDiscount(self): #discount of a book if self.discount > 0: return (int(self.discount) / 100) * int(self.price) return 0
class Media(models.Model): media_type = None help_text = None icon = None # details name = models.CharField(max_length=120, null=True, blank=True) description = models.TextField(null=True, blank=True) slug = models.SlugField( unique=True, blank=True, help_text=_('Leave this field blank for autopopulating a unique tag ' 'based on the objects name.')) # categorization tags = TaggableManager(blank=True) content_type = models.ForeignKey(ContentType, editable=False) created = models.DateTimeField(auto_now_add=True) # managers objects = MediaManager() class Meta: app_label = 'mediastore' verbose_name = _('media') verbose_name_plural = _('media') ordering = ('created', ) def __repr__(self): name = 'Media' if self.__class__ is not Media: name = 'Media:%s' % self.__class__.__name__ return '<%s: pk=%d "%s">' % (name, self.pk, self.name or '') def __unicode__(self): return self.name or '' def get_media_type(self): if self.media_type: return self.media_type return self.content_type.model_class().media_type def generate_slug(self): from mediastore.utils import unique_slug return unique_slug(self.name or self.pk, Media) def save(self, *args, **kwargs): if self.content_type_id is None: media_content_type = ContentType.objects.get_for_model(Media) content_type = ContentType.objects.get_for_model(self) if content_type != media_content_type: self.content_type = content_type if not self.content_type: raise AssertionError( '''This media instance has no associated media type. Access Media only by subclasses.''' ) if not self.slug: self.slug = self.generate_slug() super(Media, self).save(*args, **kwargs) @property def object(self): ''' Returns the related media item. ''' if not hasattr(self, '_related_media_instance'): self._related_media_instance = None if is_media_instance(self): self._related_media_instance = self else: model = self.content_type.model_class() o2o_rel = model._meta.get_field('media_ptr') attname = o2o_rel.related_query_name() self._related_media_instance = getattr(self, attname) return self._related_media_instance
class Project(ModelBase): """Placeholder model for projects.""" object_type = object_types['group'] name = models.CharField(max_length=100) # Select kind of project (study group, course, or other) STUDY_GROUP = 'study group' COURSE = 'course' CHALLENGE = 'challenge' CATEGORY_CHOICES = ( (STUDY_GROUP, _('Study Group -- group of people working ' \ 'collaboratively to acquire and share knowledge.')), (COURSE, _('Course -- led by one or more organizers with skills on ' \ 'a field who direct and help participants during their ' \ 'learning.')), (CHALLENGE, _('Challenge -- series of tasks peers can engage in ' \ 'to develop skills.')) ) category = models.CharField(max_length=30, choices=CATEGORY_CHOICES, default=STUDY_GROUP, null=True, blank=False) tags = TaggableManager(through=GeneralTaggedItem, blank=True) other = models.CharField(max_length=30, blank=True, null=True) other_description = models.CharField(max_length=150, blank=True, null=True) short_description = models.CharField(max_length=150) long_description = RichTextField(validators=[MaxLengthValidator(700)]) start_date = models.DateField(null=True, blank=True) end_date = models.DateField(null=True, blank=True) school = models.ForeignKey('schools.School', related_name='projects', null=True, blank=True) detailed_description = models.ForeignKey('content.Page', related_name='desc_project', null=True, blank=True) image = models.ImageField(upload_to=determine_image_upload_path, null=True, storage=storage.ImageStorage(), blank=True) slug = models.SlugField(unique=True, max_length=110) featured = models.BooleanField(default=False) created_on = models.DateTimeField(auto_now_add=True, default=datetime.datetime.now) under_development = models.BooleanField(default=True) not_listed = models.BooleanField(default=False) archived = models.BooleanField(default=False) clone_of = models.ForeignKey('projects.Project', blank=True, null=True, related_name='derivated_projects') imported_from = models.CharField(max_length=150, blank=True, null=True) next_projects = models.ManyToManyField('projects.Project', symmetrical=False, related_name='previous_projects', blank=True, null=True) objects = ProjectManager() class Meta: verbose_name = _('group') def __unicode__(self): return _('%(name)s %(kind)s') % dict(name=self.name, kind=self.kind.lower()) @models.permalink def get_absolute_url(self): return ('projects_show', (), { 'slug': self.slug, }) def friendly_verb(self, verb): if verbs['post'] == verb: return _('created') @property def kind(self): return self.other.lower() if self.other else self.category def followers(self, include_deleted=False): relationships = Relationship.objects.all() if not include_deleted: relationships = relationships.filter(source__deleted=False) return relationships.filter(target_project=self, deleted=False) def previous_followers(self, include_deleted=False): """Return a list of users who were followers if this project.""" relationships = Relationship.objects.all() if not include_deleted: relationships = relationships.filter(source__deleted=False) return relationships.filter(target_project=self, deleted=True) def non_participant_followers(self, include_deleted=False): return self.followers(include_deleted).exclude( source__id__in=self.participants(include_deleted).values( 'user_id')) def participants(self, include_deleted=False): """Return a list of users participating in this project.""" participations = Participation.objects.all() if not include_deleted: participations = participations.filter(user__deleted=False) return participations.filter(project=self, left_on__isnull=True) def non_organizer_participants(self, include_deleted=False): return self.participants(include_deleted).filter(organizing=False) def adopters(self, include_deleted=False): return self.participants(include_deleted).filter( Q(adopter=True) | Q(organizing=True)) def non_adopter_participants(self, include_deleted=False): return self.non_organizer_participants(include_deleted).filter( adopter=False) def organizers(self, include_deleted=False): return self.participants(include_deleted).filter(organizing=True) def is_organizing(self, user): if user.is_authenticated(): profile = user.get_profile() is_organizer = self.organizers().filter(user=profile).exists() is_superuser = user.is_superuser return is_organizer or is_superuser else: return False def is_following(self, user): if user.is_authenticated(): profile = user.get_profile() is_following = self.followers().filter(source=profile).exists() return is_following else: return False def is_participating(self, user): if user.is_authenticated(): profile = user.get_profile() is_organizer_or_participant = self.participants().filter( user=profile).exists() is_superuser = user.is_superuser return is_organizer_or_participant or is_superuser else: return False def get_metrics_permissions(self, user): """Provides metrics related permissions for metrics overview and csv download.""" if user.is_authenticated(): if user.is_superuser: return True, True allowed_schools = settings.STATISTICS_ENABLED_SCHOOLS if not self.school or self.school.slug not in allowed_schools: return False, False csv_downloaders = settings.STATISTICS_CSV_DOWNLOADERS profile = user.get_profile() csv_permission = profile.username in csv_downloaders is_school_organizer = self.school.organizers.filter( id=user.id).exists() if is_school_organizer or self.is_organizing(user): return True, csv_permission return False, False def activities(self): return Activity.objects.filter( deleted=False, scope_object=self).order_by('-created_on') def create(self): self.save() self.send_creation_notification() def save(self): """Make sure each project has a unique slug.""" count = 1 if not self.slug: slug = slugify(self.name) self.slug = slug while True: existing = Project.objects.filter(slug=self.slug) if len(existing) == 0: break self.slug = "%s-%s" % (slug, count + 1) count += 1 super(Project, self).save() def get_image_url(self): missing = settings.MEDIA_URL + 'images/project-missing.png' image_path = self.image.url if self.image else missing return image_path def send_creation_notification(self): """Send notification when a new project is created.""" context = { 'project': self, 'domain': Site.objects.get_current().domain, } subjects, bodies = localize_email( 'projects/emails/project_created_subject.txt', 'projects/emails/project_created.txt', context) for organizer in self.organizers(): SendUserEmail.apply_async((organizer.user, subjects, bodies)) admin_subject = render_to_string( "projects/emails/admin_project_created_subject.txt", context).strip() admin_body = render_to_string( "projects/emails/admin_project_created.txt", context).strip() for admin_email in settings.ADMIN_PROJECT_CREATE_EMAIL: send_mail(admin_subject, admin_body, admin_email, [admin_email], fail_silently=True) def accepted_school(self): # Used previously when schools had to decline groups. return self.school def check_tasks_completion(self, user): total_count = self.pages.filter(listed=True, deleted=False).count() completed_count = PerUserTaskCompletion.objects.filter( page__project=self, page__deleted=False, unchecked_on__isnull=True, user=user).count() if total_count == completed_count: badges = self.get_project_badges(only_self_completion=True) for badge in badges: badge.award_to(user) def completed_tasks_users(self): total_count = self.pages.filter(listed=True, deleted=False).count() completed_stats = PerUserTaskCompletion.objects.filter( page__project=self, page__deleted=False, unchecked_on__isnull=True).values('user__username').annotate( completed_count=Count('page')).filter( completed_count=total_count) usernames = completed_stats.values('user__username') return Relationship.objects.filter(source__username__in=usernames, target_project=self, source__deleted=False) def get_project_badges(self, only_self_completion=False, only_peer_skill=False, only_peer_community=False): from badges.models import Badge assessment_types = [] badge_types = [] if not only_self_completion and not only_peer_community: assessment_types.append(Badge.PEER) badge_types.append(Badge.SKILL) if not only_peer_skill and not only_peer_community: assessment_types.append(Badge.SELF) badge_types.append(Badge.COMPLETION) if not only_peer_skill and not only_self_completion: assessment_types.append(Badge.PEER) badge_types.append(Badge.COMMUNITY) if assessment_types and badge_types: return self.badges.filter(assessment_type__in=assessment_types, badge_type__in=badge_types) else: return Badge.objects.none() def get_upon_completion_badges(self, user): from badges.models import Badge, Award if user.is_authenticated(): profile = user.get_profile() awarded_badges = Award.objects.filter( user=profile).values('badge_id') self_completion_badges = self.get_project_badges( only_self_completion=True) upon_completion_badges = [] for badge in self_completion_badges: missing_prerequisites = badge.prerequisites.exclude( id__in=awarded_badges).exclude( id__in=self_completion_badges.values('id')) if not missing_prerequisites.exists(): upon_completion_badges.append(badge.id) return Badge.objects.filter(id__in=upon_completion_badges) else: return Badge.objects.none() def get_awarded_badges(self, user, only_peer_skill=False): from badges.models import Badge, Award if user.is_authenticated(): profile = user.get_profile() awarded_badges = Award.objects.filter( user=profile).values('badge_id') project_badges = self.get_project_badges(only_peer_skill) return project_badges.filter(id__in=awarded_badges) else: return Badge.objects.none() def get_badges_in_progress(self, user): from badges.models import Badge, Award, Submission if user.is_authenticated(): profile = user.get_profile() awarded_badges = Award.objects.filter( user=profile).values('badge_id') attempted_badges = Submission.objects.filter( author=profile).values('badge_id') project_badges = self.get_project_badges(only_peer_skill=True) return project_badges.filter(id__in=attempted_badges).exclude( id__in=awarded_badges) else: return Badge.objects.none() def get_non_attempted_badges(self, user): from badges.models import Badge, Award, Submission if user.is_authenticated(): profile = user.get_profile() awarded_badges = Award.objects.filter( user=profile).values('badge_id') attempted_badges = Submission.objects.filter( author=profile).values('badge_id') project_badges = self.get_project_badges(only_peer_skill=True) # Excluding both awarded and attempted badges # In case honorary award do not rely on submissions. return project_badges.exclude(id__in=attempted_badges).exclude( id__in=awarded_badges) else: return Badge.objects.none() def get_need_reviews_badges(self, user): from badges.models import Badge, Award, Submission if user.is_authenticated(): profile = user.get_profile() project_badges = self.get_project_badges(only_peer_skill=True) peers_submissions = Submission.objects.filter( badge__id__in=project_badges.values('id')).exclude( author=profile) peers_attempted_badges = project_badges.filter( id__in=peers_submissions.values('badge_id')) need_reviews_badges = [] for badge in peers_attempted_badges: peers_awards = Award.objects.filter(badge=badge).exclude( user=profile) pending_submissions = peers_submissions.filter( badge=badge).exclude( author__id__in=peers_awards.values('user_id')) if pending_submissions.exists(): need_reviews_badges.append(badge.id) return project_badges.filter(id__in=need_reviews_badges) else: return Badge.objects.none() def get_non_started_next_projects(self, user): """To be displayed in the Join Next Challenges section.""" if user.is_authenticated(): profile = user.get_profile() joined = Participation.objects.filter( user=profile).values('project_id') return self.next_projects.exclude(id__in=joined) else: return Project.objects.none() @staticmethod def filter_activities(activities): from statuses.models import Status content_types = [ ContentType.objects.get_for_model(Page), ContentType.objects.get_for_model(PageComment), ContentType.objects.get_for_model(Status), ContentType.objects.get_for_model(Project), ] return activities.filter(target_content_type__in=content_types) @staticmethod def filter_learning_activities(activities): pages_ct = ContentType.objects.get_for_model(Page) comments_ct = ContentType.objects.get_for_model(PageComment) return activities.filter( target_content_type__in=[pages_ct, comments_ct])
class Instance(models.Model): json = JSONField(default={}, null=False) xml = models.TextField() user = models.ForeignKey(User, related_name='instances', null=True) xform = models.ForeignKey(XForm, null=True, related_name='instances') survey_type = models.ForeignKey(SurveyType) # shows when we first received this instance date_created = models.DateTimeField(auto_now_add=True) # this will end up representing "date last parsed" date_modified = models.DateTimeField(auto_now=True) # this will end up representing "date instance was deleted" deleted_at = models.DateTimeField(null=True, default=None) # ODK keeps track of three statuses for an instance: # incomplete, submitted, complete # we add a fourth status: submitted_via_web status = models.CharField(max_length=20, default=u'submitted_via_web') uuid = models.CharField(max_length=249, default=u'') version = models.CharField(max_length=XFORM_TITLE_LENGTH, null=True) # store an geographic objects associated with this instance geom = models.GeometryCollectionField(null=True) objects = models.GeoManager() tags = TaggableManager() class Meta: app_label = 'logger' @classmethod def set_deleted_at(cls, instance_id, deleted_at=timezone.now()): try: instance = cls.objects.get(id=instance_id) except cls.DoesNotExist: pass else: instance.set_deleted(deleted_at) def _check_active(self, force): """Check that form is active and raise exception if not. :param force: Ignore restrictions on saving. """ if not force and self.xform and not self.xform.downloadable: raise FormInactiveError() def _set_geom(self): xform = self.xform data_dictionary = xform.data_dictionary() geo_xpaths = data_dictionary.geopoint_xpaths() doc = self.get_dict() points = [] if len(geo_xpaths): for xpath in geo_xpaths: geometry = [float(s) for s in doc.get(xpath, u'').split()] if len(geometry): lat, lng = geometry[0:2] points.append(Point(lng, lat)) if not xform.instances_with_geopoints and len(points): xform.instances_with_geopoints = True xform.save() self.geom = GeometryCollection(points) def _set_json(self): doc = self.get_dict() if not self.date_created: now = submission_time() self.date_created = now point = self.point if point: doc[GEOLOCATION] = [point.y, point.x] doc[SUBMISSION_TIME] = self.date_created.strftime(MONGO_STRFTIME) doc[XFORM_ID_STRING] = self._parser.get_xform_id_string() doc[SUBMITTED_BY] = self.user.username\ if self.user is not None else None self.json = doc def _set_parser(self): if not hasattr(self, "_parser"): self._parser = XFormInstanceParser( self.xml, self.xform.data_dictionary()) def _set_survey_type(self): self.survey_type, created = \ SurveyType.objects.get_or_create(slug=self.get_root_node_name()) def _set_uuid(self): if self.xml and not self.uuid: uuid = get_uuid_from_xml(self.xml) if uuid is not None: self.uuid = uuid set_uuid(self) def get(self, abbreviated_xpath): self._set_parser() return self._parser.get(abbreviated_xpath) def get_dict(self, force_new=False, flat=True): """Return a python object representation of this instance's XML.""" self._set_parser() return self._parser.get_flat_dict_with_attributes() if flat else\ self._parser.to_dict() def get_full_dict(self): # TODO should we store all of these in the JSON no matter what? d = self.json data = { UUID: self.uuid, ID: self.id, BAMBOO_DATASET_ID: self.xform.bamboo_dataset, self.USERFORM_ID: u'%s_%s' % ( self.user.username, self.xform.id_string), ATTACHMENTS: [a.media_file.name for a in self.attachments.all()], self.STATUS: self.status, TAGS: list(self.tags.names()), NOTES: self.get_notes(), VERSION: self.version } if isinstance(self.instance.deleted_at, datetime): data[DELETEDAT] = self.deleted_at.strftime(MONGO_STRFTIME) d.update(data) return d def get_notes(self): return [note['note'] for note in self.notes.values('note')] def get_root_node(self): self._set_parser() return self._parser.get_root_node() def get_root_node_name(self): self._set_parser() return self._parser.get_root_node_name() @property def point(self): gc = self.geom if gc and len(gc): return gc[0] def save(self, *args, **kwargs): force = kwargs.get('force') if force: del kwargs['force'] self._check_active(force) self._set_geom() self._set_json() self._set_survey_type() self._set_uuid() self.version = self.xform.version super(Instance, self).save(*args, **kwargs) def set_deleted(self, deleted_at=timezone.now()): self.deleted_at = deleted_at self.save() # force submission count re-calculation self.xform.submission_count(force_update=True) self.parsed_instance.save()
class Poll(models.Model): poll_collection = models.ForeignKey(PollCollection, on_delete=models.CASCADE, related_name='polls') RADIO = 'RA' CHECKBOX = 'CE' TEXT = 'TX' PRIO = 'PR' YESNONONE = 'Y3' TYPE_CHOICES = ( (RADIO, 'Einfachauswahl'), (CHECKBOX, 'Mehrfachauswahl'), (TEXT, 'Freitext'), (PRIO, 'Priorisierung'), (YESNONONE, 'Ja-Nein-Enthaltung'), ) poll_type = models.CharField(max_length=2, choices=TYPE_CHOICES, default=RADIO) question = models.CharField(max_length=255, unique=False) description = models.TextField(default="") is_published = models.BooleanField(default=True) show_percent = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) tags = TaggableManager(blank=True) position = models.SmallIntegerField(default=1) max_votes = models.SmallIntegerField(default=-1, blank=True, null=True) class Meta: verbose_name = "Frage" verbose_name_plural = "Fragen" ordering = ["position", "id"] def __unicode__(self): return self.question def __str__(self): return self.question @property def is_text(self): return self.poll_type == Poll.TEXT @property def is_prio(self): return self.poll_type == Poll.PRIO @property def is_yes_no_none(self): return self.poll_type == Poll.YESNONONE @property def items(self): return Item.objects.filter(poll=self).order_by('position', 'id') @property def results(self): ret = [] for item in self.items: count = self.vote_count if count == 0: ret += [(item, 0, 0)] else: if self.is_prio: votes = Vote.objects.filter(item=item) if len(votes): avg = sum(int(v.text) for v in votes) / float(votes.count()) else: avg = 0.0 ret += [(item, "%.1f" % avg, int(avg / 5.0 * 100))] elif self.is_yes_no_none: votes = Vote.objects.filter(item=item) stimmen = dict(ja=0, nein=0, enthaltung=0) for v in votes: stimmen[v.text] += 1 label = "Ja: {ja}, Nein: {nein}, Enthaltung: {enthaltung}".format( **stimmen) ret += [(item, label, 0)] else: percent = int(item.vote_count / float(count) * 100) ret += [(item, percent, percent)] return ret @property def votes(self): return Vote.objects \ .filter(poll=self) def has_voted(self, user): return Vote.objects \ .filter(poll=self, user=user)\ .exists() def get_unvoted(self, user): polls = Poll.objects.all()\ .exclude(vote__user=me)\ .filter(poll_collection__is_active=True, poll_collection__is_published=True) return [p for p in polls if p.can_vote(user)] @property def vote_count(self): return self.votes.aggregate( votes=Count('user', distinct=True))['votes']
class ResourceBase(models.Model, PermissionLevelMixin, ThumbnailMixin): """ Base Resource Object loosely based on ISO 19115:2003 """ VALID_DATE_TYPES = [(x.lower(), _(x)) for x in ['Creation', 'Publication', 'Revision']] # internal fields uuid = models.CharField(max_length=36) owner = models.ForeignKey(User, blank=True, null=True) contacts = models.ManyToManyField(Profile, through='ContactRole') # section 1 title = models.CharField( _('title'), max_length=255, help_text=_('name by which the cited resource is known')) date = models.DateTimeField( _('date'), default=datetime.now, help_text=_('reference date for the cited resource' )) # passing the method itself, not the result date_type = models.CharField( _('date type'), max_length=255, choices=VALID_DATE_TYPES, default='publication', help_text=_('identification of when a given event occurred')) edition = models.CharField(_('edition'), max_length=255, blank=True, null=True, help_text=_('version of the cited resource')) abstract = models.TextField( _('abstract'), blank=True, help_text=_( 'brief narrative summary of the content of the resource(s)')) purpose = models.TextField( _('purpose'), null=True, blank=True, help_text=_( 'summary of the intentions with which the resource(s) was developed' )) maintenance_frequency = models.CharField( _('maintenance frequency'), max_length=255, choices=UPDATE_FREQUENCIES, blank=True, null=True, help_text= _('frequency with which modifications and deletions are made to the data after it is first produced' )) # section 2 # see poc property definition below # section 3 keywords = TaggableManager( _('keywords'), blank=True, help_text= _('commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject (space or comma-separated' )) regions = models.ManyToManyField( Region, verbose_name=_('keywords region'), help_text=_('keyword identifies a location'), blank=True) restriction_code_type = models.ForeignKey( RestrictionCodeType, verbose_name=_('restrictions'), help_text=_( 'limitation(s) placed upon the access or use of the data.'), null=True, blank=True, limit_choices_to=Q(is_choice=True)) constraints_other = models.TextField( _('restrictions other'), blank=True, null=True, help_text= _('other restrictions and legal prerequisites for accessing and using the resource or metadata' )) # Section 4 language = models.CharField( _('language'), max_length=3, choices=ALL_LANGUAGES, default='eng', help_text=_('language used within the dataset')) category = models.ForeignKey( TopicCategory, help_text= _('high-level geographic data thematic classification to assist in the grouping and search of available geographic data sets.' ), null=True, blank=True, limit_choices_to=Q(is_choice=True), default=get_default_category) spatial_representation_type = models.ForeignKey( SpatialRepresentationType, help_text=_( 'method used to represent geographic information in the dataset.'), null=True, blank=True, limit_choices_to=Q(is_choice=True)) # Section 5 temporal_extent_start = models.DateField( _('temporal extent start'), blank=True, null=True, help_text=_( 'time period covered by the content of the dataset (start)')) temporal_extent_end = models.DateField( _('temporal extent end'), blank=True, null=True, help_text=_('time period covered by the content of the dataset (end)')) supplemental_information = models.TextField( _('supplemental information'), default=DEFAULT_SUPPLEMENTAL_INFORMATION, help_text=_('any other descriptive information about the dataset')) # Section 6 distribution_url = models.TextField( _('distribution URL'), blank=True, null=True, help_text= _('information about on-line sources from which the dataset, specification, or community profile name and extended metadata elements can be obtained' )) distribution_description = models.TextField( _('distribution description'), blank=True, null=True, help_text=_( 'detailed text description of what the online resource is/does')) # Section 8 data_quality_statement = models.TextField( _('data quality statement'), blank=True, null=True, help_text= _('general explanation of the data producer\'s knowledge about the lineage of a dataset' )) # Section 9 # see metadata_author property definition below # Save bbox values in the database. # This is useful for spatial searches and for generating thumbnail images and metadata records. bbox_x0 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) bbox_x1 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) bbox_y0 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) bbox_y1 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) srid = models.CharField(max_length=255, default='EPSG:4326') # CSW specific fields csw_typename = models.CharField(_('CSW typename'), max_length=32, default='gmd:MD_Metadata', null=False) csw_schema = models.CharField(_('CSW schema'), max_length=64, default='http://www.isotc211.org/2005/gmd', null=False) csw_mdsource = models.CharField(_('CSW source'), max_length=256, default='local', null=False) csw_insert_date = models.DateTimeField(_('CSW insert date'), auto_now_add=True, null=True) csw_type = models.CharField(_('CSW type'), max_length=32, default='dataset', null=False, choices=HIERARCHY_LEVELS) csw_anytext = models.TextField(_('CSW anytext'), null=True, blank=True) csw_wkt_geometry = models.TextField( _('CSW WKT geometry'), null=False, default='POLYGON((-180 -90,-180 90,180 90,180 -90,-180 -90))') # metadata XML specific fields metadata_uploaded = models.BooleanField(default=False) metadata_xml = models.TextField( null=True, default= '<gmd:MD_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd"/>', blank=True) thumbnail = models.ForeignKey(Thumbnail, null=True, blank=True) def __unicode__(self): return self.title @property def bbox(self): return [ self.bbox_x0, self.bbox_y0, self.bbox_x1, self.bbox_y1, self.srid ] @property def bbox_string(self): return ",".join([ str(self.bbox_x0), str(self.bbox_y0), str(self.bbox_x1), str(self.bbox_y1) ]) @property def geographic_bounding_box(self): return bbox_to_wkt(self.bbox_x0, self.bbox_x1, self.bbox_y0, self.bbox_y1, srid=self.srid) def get_extent(self): """Generate minx/miny/maxx/maxy of map extent""" return self.bbox @property def poc_role(self): role = Role.objects.get(value='pointOfContact') return role @property def metadata_author_role(self): role = Role.objects.get(value='author') return role def keyword_list(self): return [kw.name for kw in self.keywords.all()] @property def keyword_csv(self): keywords_qs = self.keywords.all() if keywords_qs: return ','.join([kw.name for kw in keywords_qs]) else: return '' def set_latlon_bounds(self, box): """ Set the four bounds in lat lon projection """ self.bbox_x0 = box[0] self.bbox_x1 = box[1] self.bbox_y0 = box[2] self.bbox_y1 = box[3] def download_links(self): """assemble download links for pycsw""" links = [] for url in self.link_set.all(): if url.link_type == 'metadata': # avoid recursion continue if url.link_type == 'html': links.append((self.title, 'Web address (URL)', 'WWW:LINK-1.0-http--link', url.url)) else: description = '%s (%s Format)' % (self.title, url.name) links.append((self.title, description, 'WWW:DOWNLOAD-1.0-http--download', url.url)) return links def _set_poc(self, poc): # reset any poc asignation to this resource ContactRole.objects.filter(role=self.poc_role, resource=self).delete() #create the new assignation ContactRole.objects.create(role=self.poc_role, resource=self, contact=poc) def _get_poc(self): try: the_poc = ContactRole.objects.get(role=self.poc_role, resource=self).contact except ContactRole.DoesNotExist: the_poc = None return the_poc poc = property(_get_poc, _set_poc) def _set_metadata_author(self, metadata_author): # reset any metadata_author asignation to this resource ContactRole.objects.filter(role=self.metadata_author_role, resource=self).delete() #create the new assignation ContactRole.objects.create(role=self.metadata_author_role, resource=self, contact=metadata_author) def _get_metadata_author(self): try: the_ma = ContactRole.objects.get(role=self.metadata_author_role, resource=self).contact except ContactRole.DoesNotExist: the_ma = None return the_ma metadata_author = property(_get_metadata_author, _set_metadata_author)
class Version(models.Model): project = models.ForeignKey(Project, verbose_name=_('Project'), related_name='versions') type = models.CharField( _('Type'), max_length=20, choices=VERSION_TYPES, default='unknown', ) # used by the vcs backend identifier = models.CharField(_('Identifier'), max_length=255) verbose_name = models.CharField(_('Verbose Name'), max_length=255) slug = models.CharField(_('Slug'), max_length=255) supported = models.BooleanField(_('Supported'), default=True) active = models.BooleanField(_('Active'), default=False) built = models.BooleanField(_('Built'), default=False) uploaded = models.BooleanField(_('Uploaded'), default=False) privacy_level = models.CharField( _('Privacy Level'), max_length=20, choices=constants.PRIVACY_CHOICES, default='public', help_text=_("Level of privacy for this Version.")) tags = TaggableManager(blank=True) objects = VersionManager() class Meta: unique_together = [('project', 'slug')] ordering = ['-verbose_name'] permissions = ( # Translators: Permission around whether a user can view the # version ('view_version', _('View Version')), ) def __unicode__(self): return ugettext(u"Version %(version)s of %(project)s (%(pk)s)" % { 'version': self.verbose_name, 'project': self.project, 'pk': self.pk }) def get_absolute_url(self): if not self.built and not self.uploaded: return '' return self.project.get_docs_url(version_slug=self.slug) def save(self, *args, **kwargs): """ Add permissions to the Version for all owners on save. """ obj = super(Version, self).save(*args, **kwargs) for owner in self.project.users.all(): assign('view_version', owner, self) self.project.sync_supported_versions() return obj @property def remote_slug(self): if self.slug == 'latest': if self.project.default_branch: return self.project.default_branch else: return self.project.vcs_repo().fallback_branch else: return self.slug def get_subdomain_url(self): use_subdomain = getattr(settings, 'USE_SUBDOMAIN', False) if use_subdomain: return "/%s/%s/" % ( self.project.language, self.slug, ) else: return reverse('docs_detail', kwargs={ 'project_slug': self.project.slug, 'lang_slug': self.project.language, 'version_slug': self.slug, 'filename': '' }) def get_subproject_url(self): return "/projects/%s/%s/%s/" % ( self.project.slug, self.project.language, self.slug, ) def get_downloads(self, pretty=False): project = self.project data = {} if pretty: if project.has_pdf(self.slug): data['PDF'] = project.get_pdf_url(self.slug) if project.has_htmlzip(self.slug): data['HTML'] = project.get_htmlzip_url(self.slug) if project.has_epub(self.slug): data['Epub'] = project.get_epub_url(self.slug) else: if project.has_pdf(self.slug): data['pdf_url'] = project.get_pdf_url(self.slug) if project.has_htmlzip(self.slug): data['htmlzip_url'] = project.get_htmlzip_url(self.slug) if project.has_epub(self.slug): data['epub_url'] = project.get_epub_url(self.slug) #if project.has_manpage(self.slug): #data['manpage_url'] = project.get_manpage_url(self.slug) if project.has_dash(self.slug): data['dash_url'] = project.get_dash_url(self.slug) data['dash_feed_url'] = project.get_dash_feed_url(self.slug) return data def get_conf_py_path(self): # Hack this for now. return "/docs/" conf_py_path = self.project.conf_file(self.slug) conf_py_path = conf_py_path.replace( self.project.checkout_path(self.slug), '') return conf_py_path.replace('conf.py', '') def get_build_path(self): '''Return version build path if path exists, otherwise `None`''' path = self.project.checkout_path(version=self.slug) if os.path.exists(path): return path return None def get_github_url(self, docroot, filename, source_suffix='.rst', action='view'): GITHUB_REGEXS = [ re.compile('github.com/(.+)/(.+)(?:\.git){1}'), re.compile('github.com/(.+)/(.+)'), re.compile('github.com:(.+)/(.+).git'), ] GITHUB_URL = 'https://github.com/{user}/{repo}/{action}/{version}{docroot}{path}{source_suffix}' repo_url = self.project.repo if 'github' not in repo_url: return '' if not docroot: return '' if action == 'view': action_string = 'blob' elif action == 'edit': action_string = 'edit' for regex in GITHUB_REGEXS: match = regex.search(repo_url) if match: user, repo = match.groups() break else: return '' repo = repo.rstrip('/') return GITHUB_URL.format( user=user, repo=repo, version=self.remote_slug, docroot=docroot, path=filename, source_suffix=source_suffix, action=action_string, ) def get_bitbucket_url(self, docroot, filename, source_suffix='.rst'): BB_REGEXS = [ re.compile('bitbucket.org/(.+)/(.+).git'), re.compile('bitbucket.org/(.+)/(.+)/'), re.compile('bitbucket.org/(.+)/(.+)'), ] BB_URL = 'https://bitbucket.org/{user}/{repo}/src/{version}{docroot}{path}{source_suffix}' repo_url = self.project.repo if 'bitbucket' not in repo_url: return '' if not docroot: return '' for regex in BB_REGEXS: match = regex.search(repo_url) if match: user, repo = match.groups() break else: return '' repo = repo.rstrip('/') return BB_URL.format( user=user, repo=repo, version=self.remote_slug, docroot=docroot, path=filename, source_suffix=source_suffix, )
class Conversation(HasFavoriteMixin, TimeStampedModel): """ A topic of conversation. """ title = models.CharField( _("Title"), max_length=255, help_text=_( "Short description used to create URL slugs (e.g. School system)." ), ) text = models.TextField(_("Question"), help_text=_("What do you want to ask?")) author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="conversations", help_text= _("Only the author and administrative staff can edit this conversation." ), ) moderators = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name="moderated_conversations", help_text=_("Moderators can accept and reject comments."), ) slug = AutoSlugField(unique=False, populate_from="title") is_promoted = models.BooleanField( _("Promote conversation?"), default=False, help_text=_( "Promoted conversations appears in the main /conversations/ " "endpoint."), ) is_hidden = models.BooleanField( _("Hide conversation?"), default=False, help_text=_( "Hidden conversations does not appears in boards or in the main /conversations/ " "endpoint."), ) objects = ConversationQuerySet.as_manager() tags = TaggableManager(through="ConversationTag", blank=True) votes = property( lambda self: Vote.objects.filter(comment__conversation=self)) @property def users(self): return get_user_model().objects.filter( votes__comment__conversation=self).distinct() # Comment managers def _filter_comments(*args): *_, which = args status = getattr(Comment.STATUS, which) return property(lambda self: self.comments.filter(status=status)) approved_comments = _filter_comments("approved") rejected_comments = _filter_comments("rejected") pending_comments = _filter_comments("pending") del _filter_comments class Meta: ordering = ["created"] verbose_name = _("Conversation") verbose_name_plural = _("Conversations") permissions = ( ("can_publish_promoted", _("Can publish promoted conversations")), ("is_moderator", _("Can moderate comments in any conversation")), ) # # Statistics and annotated values # author_name = lazy(this.author.name) first_tag = lazy(this.tags.values_list("name", flat=True).first()) tag_names = lazy(this.tags.values_list("name", flat=True)) # Statistics n_comments = deprecate_lazy( this.n_approved_comments, "Conversation.n_comments was deprecated in favor of .n_approved_comments." ) n_approved_comments = lazy(this.approved_comments.count()) n_pending_comments = lazy(this.pending_comments.count()) n_rejected_comments = lazy(this.rejected_comments.count()) n_total_comments = lazy(this.comments.count().count()) n_favorites = lazy(this.favorites.count()) n_tags = lazy(this.tags.count()) n_votes = lazy(this.votes.count()) n_final_votes = lazy(this.votes.exclude(choice=Choice.SKIP).count()) n_participants = lazy(this.users.count()) # Statistics for the request user user_comments = property(this.comments.filter(author=this.for_user)) user_votes = property(this.votes.filter(author=this.for_user)) n_user_total_comments = lazy(this.user_comments.count()) n_user_comments = lazy( this.user_comments.filter(status=Comment.STATUS.approved).count()) n_user_rejected_comments = lazy( this.user_comments.filter(status=Comment.STATUS.rejected).count()) n_user_pending_comments = lazy( this.user_comments.filter(status=Comment.STATUS.pending).count()) n_user_votes = lazy(this.user_votes.count()) n_user_final_votes = lazy( this.user_votes.exclude(choice=Choice.SKIP).count()) is_user_favorite = lazy(this.is_favorite(this.for_user)) # Statistical methods vote_count = vote_count statistics = statistics statistics_for_user = statistics_for_user @lazy def for_user(self): return self.request.user @lazy def request(self): msg = "Set the request object by calling the .set_request(request) method first" raise RuntimeError(msg) # TODO: move as patches from other apps @lazy def n_clusters(self): try: return self.clusterization.n_clusters except AttributeError: return 0 @lazy def n_stereotypes(self): try: return self.clusterization.n_clusters except AttributeError: return 0 n_endorsements = 0 # FIXME: endorsements def __str__(self): return self.title def set_request(self, request_or_user): """ Saves optional user and request attributes in model. Those attributes are used to compute and cache many other attributes and statistics in the conversation model instance. """ request = None user = request_or_user if not isinstance(request_or_user, get_user_model()): user = request_or_user.user request = request_or_user if self.__dict__.get("for_user", user) != user or self.__dict__.get( "request", request) != request: raise ValueError("user/request already set in conversation!") self.for_user = user self.request = request def save(self, *args, **kwargs): if self.id is None: pass super().save(*args, **kwargs) def clean(self): can_edit = "ej.can_edit_conversation" if self.is_promoted and self.author_id is not None and not self.author.has_perm( can_edit, self): raise ValidationError( _("User does not have permission to create a promoted " "conversation.")) def get_absolute_url(self, board=None): kwargs = {"conversation": self, "slug": self.slug} if board is None: board = getattr(self, "board", None) if board: kwargs["board"] = board return SafeUrl("boards:conversation-detail", **kwargs) else: return SafeUrl("conversation:detail", **kwargs) def url(self, which="conversation:detail", board=None, **kwargs): """ Return a url pertaining to the current conversation. """ if board is None: board = getattr(self, "board", None) kwargs["conversation"] = self kwargs["slug"] = self.slug if board: kwargs["board"] = board which = "boards:" + which.replace(":", "-") return SafeUrl(which, **kwargs) return SafeUrl(which, **kwargs) def votes_for_user(self, user): """ Get all votes in conversation for the given user. """ if user.id is None: return Vote.objects.none() return self.votes.filter(author=user) def create_comment(self, author, content, commit=True, *, status=None, check_limits=True, **kwargs): """ Create a new comment object for the given user. If commit=True (default), comment is persisted on the database. By default, this method check if the user can post according to the limits imposed by the conversation. It also normalizes duplicate comments and reuse duplicates from the database. """ # Convert status, if necessary if status is None and (author.id == self.author.id or author.has_perm( "ej.can_edit_conversation", self)): kwargs["status"] = Comment.STATUS.approved else: kwargs["status"] = normalize_status(status) # Check limits if check_limits and not author.has_perm("ej.can_comment", self): log.info("failed attempt to create comment by %s" % author) raise PermissionError("user cannot comment on conversation.") # Check if comment is created with rejected status if status == Comment.STATUS.rejected: msg = _("automatically rejected") kwargs.setdefault("rejection_reason", msg) kwargs.update(author=author, content=content.strip()) comment = make_clean(Comment, commit, conversation=self, **kwargs) if comment.status == comment.STATUS.approved and author != self.author: comment_moderated.send( Comment, comment=comment, moderator=comment.moderator, is_approved=True, author=comment.author, ) log.info("new comment: %s" % comment) return comment def next_comment(self, user, default=NOT_GIVEN): """ Returns a random comment that user didn't vote yet. If default value is not given, raises a Comment.DoesNotExit exception if no comments are available for user. """ comment = rules.compute("ej.next_comment", self, user) if comment: return comment return None def next_comment_with_id(self, user, comment_id=None): """ Returns a comment with id if user didn't vote yet, otherwhise return a random comment. """ if comment_id: try: return self.approved_comments.exclude(votes__author=user).get( id=comment_id) except Exception as e: pass return self.next_comment(user)
class ResourceBase(PolymorphicModel, PermissionLevelMixin): """ Base Resource Object loosely based on ISO 19115:2003 """ VALID_DATE_TYPES = [(x.lower(), _(x)) for x in ['Creation', 'Publication', 'Revision']] date_help_text = _('reference date for the cited resource') date_type_help_text = _('identification of when a given event occurred') edition_help_text = _('version of the cited resource') abstract_help_text = _( 'brief narrative summary of the content of the resource(s)') purpose_help_text = _( 'summary of the intentions with which the resource(s) was developed') maintenance_frequency_help_text = _( 'frequency with which modifications and deletions are made to the data after ' 'it is first produced') keywords_help_text = _( 'commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject ' '(space or comma-separated') regions_help_text = _('keyword identifies a location') restriction_code_type_help_text = _( 'limitation(s) placed upon the access or use of the data.') constraints_other_help_text = _( 'other restrictions and legal prerequisites for accessing and using the resource or' ' metadata') license_help_text = _('license of the dataset') language_help_text = _('language used within the dataset') category_help_text = _( 'high-level geographic data thematic classification to assist in the grouping and search of ' 'available geographic data sets.') spatial_representation_type_help_text = _( 'method used to represent geographic information in the dataset.') temporal_extent_start_help_text = _( 'time period covered by the content of the dataset (start)') temporal_extent_end_help_text = _( 'time period covered by the content of the dataset (end)') data_quality_statement_help_text = _( 'general explanation of the data producer\'s knowledge about the lineage of a' ' dataset') # internal fields uuid = models.CharField(max_length=36) owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='owned_resource', verbose_name=_("Owner")) contacts = models.ManyToManyField(settings.AUTH_USER_MODEL, through='ContactRole') title = models.CharField( _('title'), max_length=255, help_text=_('name by which the cited resource is known')) date = models.DateTimeField(_('date'), default=datetime.datetime.now, help_text=date_help_text) date_type = models.CharField(_('date type'), max_length=255, choices=VALID_DATE_TYPES, default='publication', help_text=date_type_help_text) edition = models.CharField(_('edition'), max_length=255, blank=True, null=True, help_text=edition_help_text) abstract = models.TextField(_('abstract'), blank=True, help_text=abstract_help_text) purpose = models.TextField(_('purpose'), null=True, blank=True, help_text=purpose_help_text) maintenance_frequency = models.CharField( _('maintenance frequency'), max_length=255, choices=UPDATE_FREQUENCIES, blank=True, null=True, help_text=maintenance_frequency_help_text) keywords = TaggableManager(_('keywords'), blank=True, help_text=keywords_help_text) regions = models.ManyToManyField(Region, verbose_name=_('keywords region'), blank=True, null=True, help_text=regions_help_text) restriction_code_type = models.ForeignKey( RestrictionCodeType, verbose_name=_('restrictions'), help_text=restriction_code_type_help_text, null=True, blank=True, limit_choices_to=Q(is_choice=True)) constraints_other = models.TextField(_('restrictions other'), blank=True, null=True, help_text=constraints_other_help_text) license = models.ForeignKey(License, null=True, blank=True, verbose_name=_("License"), help_text=license_help_text) language = models.CharField(_('language'), max_length=3, choices=ALL_LANGUAGES, default='eng', help_text=language_help_text) category = models.ForeignKey(TopicCategory, null=True, blank=True, limit_choices_to=Q(is_choice=True), help_text=category_help_text) spatial_representation_type = models.ForeignKey( SpatialRepresentationType, null=True, blank=True, limit_choices_to=Q(is_choice=True), verbose_name=_("spatial representation type"), help_text=spatial_representation_type_help_text) # Section 5 temporal_extent_start = models.DateTimeField( _('temporal extent start'), blank=True, null=True, help_text=temporal_extent_start_help_text) temporal_extent_end = models.DateTimeField( _('temporal extent end'), blank=True, null=True, help_text=temporal_extent_end_help_text) supplemental_information = models.TextField( _('supplemental information'), default=DEFAULT_SUPPLEMENTAL_INFORMATION, help_text=_('any other descriptive information about the dataset')) # Section 8 data_quality_statement = models.TextField( _('data quality statement'), blank=True, null=True, help_text=data_quality_statement_help_text) # Section 9 # see metadata_author property definition below # Save bbox values in the database. # This is useful for spatial searches and for generating thumbnail images and metadata records. bbox_x0 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) bbox_x1 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) bbox_y0 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) bbox_y1 = models.DecimalField(max_digits=19, decimal_places=10, blank=True, null=True) srid = models.CharField(max_length=255, default='EPSG:4326') # CSW specific fields csw_typename = models.CharField(_('CSW typename'), max_length=32, default='gmd:MD_Metadata', null=False) csw_schema = models.CharField(_('CSW schema'), max_length=64, default='http://www.isotc211.org/2005/gmd', null=False) csw_mdsource = models.CharField(_('CSW source'), max_length=256, default='local', null=False) csw_insert_date = models.DateTimeField(_('CSW insert date'), auto_now_add=True, null=True) csw_type = models.CharField(_('CSW type'), max_length=32, default='dataset', null=False, choices=HIERARCHY_LEVELS) csw_anytext = models.TextField(_('CSW anytext'), null=True, blank=True) csw_wkt_geometry = models.TextField( _('CSW WKT geometry'), null=False, default='POLYGON((-180 -90,-180 90,180 90,180 -90,-180 -90))') # metadata XML specific fields metadata_uploaded = models.BooleanField(default=False) metadata_uploaded_preserve = models.BooleanField(default=False) metadata_xml = models.TextField( null=True, default= '<gmd:MD_Metadata xmlns:gmd="http://www.isotc211.org/2005/gmd"/>', blank=True) popular_count = models.IntegerField(default=0) share_count = models.IntegerField(default=0) featured = models.BooleanField( _("Featured"), default=False, help_text=_('Should this resource be advertised in home page?')) is_published = models.BooleanField( _("Is Published"), default=True, help_text=_('Should this resource be published and searchable?')) # fields necessary for the apis thumbnail_url = models.TextField(null=True, blank=True) detail_url = models.CharField(max_length=255, null=True, blank=True) rating = models.IntegerField(default=0, null=True, blank=True) def __unicode__(self): return self.title @property def bbox(self): return [ self.bbox_x0, self.bbox_y0, self.bbox_x1, self.bbox_y1, self.srid ] @property def bbox_string(self): return ",".join([ str(self.bbox_x0), str(self.bbox_y0), str(self.bbox_x1), str(self.bbox_y1) ]) @property def geographic_bounding_box(self): return bbox_to_wkt(self.bbox_x0, self.bbox_x1, self.bbox_y0, self.bbox_y1, srid=self.srid) @property def license_light(self): a = [] if (not (self.license.name is None)) and (len(self.license.name) > 0): a.append(self.license.name) if (not (self.license.url is None)) and (len(self.license.url) > 0): a.append("(" + self.license.url + ")") return " ".join(a) @property def license_verbose(self): a = [] if (not (self.license.name_long is None)) and (len( self.license.name_long) > 0): a.append(self.license.name_long + ":") if (not (self.license.description is None)) and (len( self.license.description) > 0): a.append(self.license.description) if (not (self.license.url is None)) and (len(self.license.url) > 0): a.append("(" + self.license.url + ")") return " ".join(a) def keyword_list(self): return [kw.name for kw in self.keywords.all()] def keyword_slug_list(self): return [kw.slug for kw in self.keywords.all()] def region_name_list(self): return [region.name for region in self.regions.all()] def spatial_representation_type_string(self): if hasattr(self.spatial_representation_type, 'identifier'): return self.spatial_representation_type.identifier else: if hasattr(self, 'storeType'): if self.storeType == 'coverageStore': return 'grid' return 'vector' else: return None @property def keyword_csv(self): keywords_qs = self.get_real_instance().keywords.all() if keywords_qs: return ','.join([kw.name for kw in keywords_qs]) else: return '' def set_latlon_bounds(self, box): """ Set the four bounds in lat lon projection """ self.bbox_x0 = box[0] self.bbox_x1 = box[1] self.bbox_y0 = box[2] self.bbox_y1 = box[3] def set_bounds_from_center_and_zoom(self, center_x, center_y, zoom): """ Calculate zoom level and center coordinates in mercator. """ self.center_x = center_x self.center_y = center_y self.zoom = zoom deg_len_equator = 40075160 / 360 # covert center in lat lon def get_lon_lat(): wgs84 = Proj(init='epsg:4326') mercator = Proj(init='epsg:3857') lon, lat = transform(mercator, wgs84, center_x, center_y) return lon, lat # calculate the degree length at this latitude def deg_len(): lon, lat = get_lon_lat() return math.cos(lat) * deg_len_equator lon, lat = get_lon_lat() # taken from http://wiki.openstreetmap.org/wiki/Zoom_levels # it might be not precise but enough for the purpose distance_per_pixel = 40075160 * math.cos(lat) / 2**(zoom + 8) # calculate the distance from the center of the map in degrees # we use the calculated degree length on the x axis and the # normal degree length on the y axis assumin that it does not change # Assuming a map of 1000 px of width and 700 px of height distance_x_degrees = distance_per_pixel * 500 / deg_len() distance_y_degrees = distance_per_pixel * 350 / deg_len_equator self.bbox_x0 = lon - distance_x_degrees self.bbox_x1 = lon + distance_x_degrees self.bbox_y0 = lat - distance_y_degrees self.bbox_y1 = lat + distance_y_degrees def set_bounds_from_bbox(self, bbox): """ Calculate zoom level and center coordinates in mercator. """ self.set_latlon_bounds(bbox) minx, miny, maxx, maxy = [float(c) for c in bbox] x = (minx + maxx) / 2 y = (miny + maxy) / 2 (center_x, center_y) = forward_mercator((x, y)) xdiff = maxx - minx ydiff = maxy - miny zoom = 0 if xdiff > 0 and ydiff > 0: width_zoom = math.log(360 / xdiff, 2) height_zoom = math.log(360 / ydiff, 2) zoom = math.ceil(min(width_zoom, height_zoom)) self.zoom = zoom self.center_x = center_x self.center_y = center_y def download_links(self): """assemble download links for pycsw""" links = [] for url in self.link_set.all(): if url.link_type == 'metadata': # avoid recursion continue if url.link_type == 'html': links.append((self.title, 'Web address (URL)', 'WWW:LINK-1.0-http--link', url.url)) elif url.link_type in ('OGC:WMS', 'OGC:WFS', 'OGC:WCS'): links.append((self.title, url.name, url.link_type, url.url)) else: description = '%s (%s Format)' % (self.title, url.name) links.append((self.title, description, 'WWW:DOWNLOAD-1.0-http--download', url.url)) return links def get_tiles_url(self): """Return URL for Z/Y/X mapping clients or None if it does not exist. """ try: tiles_link = self.link_set.get(name='Tiles') except Link.DoesNotExist: return None else: return tiles_link.url def get_legend(self): """Return Link for legend or None if it does not exist. """ try: legends_link = self.link_set.get(name='Legend') except Link.DoesNotExist: return None else: return legends_link def get_legend_url(self): """Return URL for legend or None if it does not exist. The legend can be either an image (for Geoserver's WMS) or a JSON object for ArcGIS. """ legend = self.get_legend() if legend is None: return None return legend.url def get_ows_url(self): """Return URL for OGC WMS server None if it does not exist. """ try: ows_link = self.link_set.get(name='OGC:WMS') except Link.DoesNotExist: return None else: return ows_link.url def get_thumbnail_url(self): """Return a thumbnail url. It could be a local one if it exists, a remote one (WMS GetImage) for example or a 'Missing Thumbnail' one. """ local_thumbnails = self.link_set.filter(name='Thumbnail') if local_thumbnails.count() > 0: return local_thumbnails[0].url remote_thumbnails = self.link_set.filter(name='Remote Thumbnail') if remote_thumbnails.count() > 0: return remote_thumbnails[0].url return staticfiles.static(settings.MISSING_THUMBNAIL) def has_thumbnail(self): """Determine if the thumbnail object exists and an image exists""" return self.link_set.filter(name='Thumbnail').exists() def save_thumbnail(self, filename, image): thumb_folder = 'thumbs' upload_path = os.path.join(settings.MEDIA_ROOT, thumb_folder) if not os.path.exists(upload_path): os.makedirs(upload_path) with open(os.path.join(upload_path, filename), 'wb') as f: thumbnail = File(f) thumbnail.write(image) url_path = os.path.join(settings.MEDIA_URL, thumb_folder, filename).replace('\\', '/') url = urljoin(settings.SITEURL, url_path) Link.objects.get_or_create(resource=self, url=url, defaults=dict( name='Thumbnail', extension='png', mime='image/png', link_type='image', )) ResourceBase.objects.filter(id=self.id).update(thumbnail_url=url) def set_missing_info(self): """Set default permissions and point of contacts. It is mandatory to call it from descendant classes but hard to enforce technically via signals or save overriding. """ from guardian.models import UserObjectPermission logger.debug('Checking for permissions.') # True if every key in the get_all_level_info dict is empty. no_custom_permissions = UserObjectPermission.objects.filter( content_type=ContentType.objects.get_for_model( self.get_self_resource()), object_pk=str(self.pk)).exists() if not no_custom_permissions: logger.debug( 'There are no permissions for this object, setting default perms.' ) self.set_default_permissions() if self.owner: user = self.owner else: user = ResourceBase.objects.admin_contact().user if self.poc is None: self.poc = user if self.metadata_author is None: self.metadata_author = user def maintenance_frequency_title(self): return [ v for i, v in enumerate(UPDATE_FREQUENCIES) if v[0] == self.maintenance_frequency ][0][1].title() def language_title(self): return [ v for i, v in enumerate(ALL_LANGUAGES) if v[0] == self.language ][0][1].title() def _set_poc(self, poc): # reset any poc assignation to this resource ContactRole.objects.filter(role='pointOfContact', resource=self).delete() # create the new assignation ContactRole.objects.create(role='pointOfContact', resource=self, contact=poc) def _get_poc(self): try: the_poc = ContactRole.objects.get(role='pointOfContact', resource=self).contact except ContactRole.DoesNotExist: the_poc = None return the_poc poc = property(_get_poc, _set_poc) def _set_metadata_author(self, metadata_author): # reset any metadata_author assignation to this resource ContactRole.objects.filter(role='author', resource=self).delete() # create the new assignation ContactRole.objects.create(role='author', resource=self, contact=metadata_author) def _get_metadata_author(self): try: the_ma = ContactRole.objects.get(role='author', resource=self).contact except ContactRole.DoesNotExist: the_ma = None return the_ma metadata_author = property(_get_metadata_author, _set_metadata_author) objects = ResourceBaseManager() class Meta: # custom permissions, # add, change and delete are standard in django-guardian permissions = ( ('view_resourcebase', 'Can view resource'), ('change_resourcebase_permissions', 'Can change resource permissions'), ('download_resourcebase', 'Can download resource'), ('publish_resourcebase', 'Can publish resource'), ('change_resourcebase_metadata', 'Can change resource metadata'), )
class Question(AbstractComment): title = models.CharField(max_length=255) tags = TaggableManager(through=TaggedQuestion) def __str__(self): return self.title
class Profile(AbstractUser): """Fully featured Geonode user""" organization = models.CharField( _('Organization Name'), max_length=255, blank=True, null=True, help_text=_('name of the responsible organization')) profile = models.TextField( _('Profile'), null=True, blank=True, help_text=_('introduce yourself')) position = models.CharField( _('Position Name'), max_length=255, blank=True, null=True, help_text=_('role or position of the responsible person')) voice = models.CharField(_('Voice'), max_length=255, blank=True, null=True, help_text=_( 'telephone number by which individuals can speak to the responsible organization or individual')) fax = models.CharField(_('Facsimile'), max_length=255, blank=True, null=True, help_text=_( 'telephone number of a facsimile machine for the responsible organization or individual')) delivery = models.CharField( _('Delivery Point'), max_length=255, blank=True, null=True, help_text=_('physical and email address at which the organization or individual may be contacted')) city = models.CharField( _('City'), max_length=255, blank=True, null=True, help_text=_('city of the location')) area = models.CharField( _('Administrative Area'), max_length=255, blank=True, null=True, help_text=_('state, province of the location')) zipcode = models.CharField( _('Postal Code'), max_length=255, blank=True, null=True, help_text=_('ZIP or other postal code')) country = models.CharField( _('Country'), choices=COUNTRIES, max_length=3, blank=True, null=True, help_text=_('country of the physical address')) keywords = TaggableManager(_('keywords'), blank=True, help_text=_( 'commonly used word(s) or formalised word(s) or phrase(s) used to describe the subject \ (space or comma-separated')) language = models.CharField( _("language"), max_length=10, choices=LANGUAGES, default=settings.LANGUAGE_CODE ) timezone = models.CharField( _('Timezone'), max_length=100, default="", choices=TIMEZONES, blank=True, ) def __init__(self, *args, **kwargs): super(Profile, self).__init__(*args, **kwargs) self._previous_active_state = self.is_active def get_absolute_url(self): return reverse('profile_detail', args=[self.username, ]) def __unicode__(self): return u"%s" % (self.username) def class_name(value): return value.__class__.__name__ objects = ProfileUserManager() USERNAME_FIELD = 'username' def group_list_public(self): return GroupProfile.objects.exclude( access="private").filter(groupmember__user=self) def group_list_all(self): return GroupProfile.objects.filter(groupmember__user=self).distinct() def is_member_of_group(self, group_slug): """ Returns if the Profile belongs to a group of a given slug. """ return self.groups.filter(name=group_slug).exists() def keyword_list(self): """ Returns a list of the Profile's keywords. """ return [kw.name for kw in self.keywords.all()] @property def name_long(self): if self.first_name and self.last_name: return '%s %s (%s)' % (self.first_name, self.last_name, self.username) elif (not self.first_name) and self.last_name: return '%s (%s)' % (self.last_name, self.username) elif self.first_name and (not self.last_name): return '%s (%s)' % (self.first_name, self.username) else: return self.username @property def location(self): return format_address(self.delivery, self.zipcode, self.city, self.area, self.country) def save(self, *args, **kwargs): super(Profile, self).save(*args, **kwargs) self._notify_account_activated() self._previous_active_state = self.is_active def _notify_account_activated(self): """Notify user that its account has been activated by a staff member""" became_active = self.is_active and not self._previous_active_state if became_active and self.last_login is None: try: # send_notification(users=(self,), label="account_active") from invitations.adapters import get_invitations_adapter current_site = Site.objects.get_current() ctx = { 'username': self.username, 'current_site': current_site, 'site_name': current_site.name, 'email': self.email, 'inviter': self, } email_template = 'pinax/notifications/account_active/account_active' get_invitations_adapter().send_mail( email_template, self.email, ctx) except BaseException: import traceback traceback.print_exc()
class Site(models.Model): """ A news website included in the archive. """ name = models.CharField( help_text='The formal name of the site that will display for users', max_length=150) sortable_name = models.CharField( help_text='The version of the name used for sorting', max_length=150) slug = models.SlugField(unique=True) url = models.URLField() display_url = models.URLField(blank=True) description = models.TextField(blank=True) hometown = models.CharField(max_length=500, blank=True) timezone = models.CharField( max_length=500, blank=True, choices=[(i, i) for i in common_timezones], ) STATUS_CHOICES = ( ('active', 'Active'), ('inactive', 'Inactive'), ) status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='active', db_index=True, ) on_the_homepage = models.BooleanField(default=True) # Local screenshots has_html_screenshots = models.BooleanField(default=False) y_offset = models.IntegerField(default=0, blank=True) # Third party mementos has_internetarchive_mementos = models.BooleanField( verbose_name='has Internet Archive mementos', default=False) has_archiveis_mementos = models.BooleanField( verbose_name='has archive.is mementos', default=False) has_webcitation_mementos = models.BooleanField( verbose_name="has webcitation.org mementos", default=False) # Managers objects = managers.SiteManager() tags = TaggableManager(blank=True) class Meta: ordering = ( 'sortable_name', 'name', ) def __unicode__(self): return self.name @models.permalink def get_absolute_url(self): return ("archive-site-detail", [self.slug]) @models.permalink def get_timemap_index_url(self): return ("timemap-url-link-feed", [], dict(url=self.url))