Beispiel #1
0
class Partner(models.Model):
    """
    A partner organization which provides access grants to paywalled resources.
    This model tracks contact information for the partner as well as extra
    information they require on access grant applications.
    """
    class Meta:
        app_label = 'resources'
        verbose_name = 'partner'
        verbose_name_plural = 'partners'
        ordering = ['company_name']

    # --------------------------------------------------------------------------
    # Managers
    # --------------------------------------------------------------------------

    # Define managers. Note that the basic manager must be first to make
    # Django internals work as expected, but we define objects as our custom
    # manager so that we don't inadvertently expose unavailable Partners to
    # end users.
    even_not_available = models.Manager()
    objects = AvailablePartnerManager()

    # --------------------------------------------------------------------------
    # Attributes
    # --------------------------------------------------------------------------

    company_name = models.CharField(max_length=40,
        # Translators: In the administrator interface, this text is help text for a field where staff can enter the name of the partner. Don't translate McFarland.
        help_text=_("Partner's name (e.g. McFarland). Note: "
        "this will be user-visible and *not translated*."))
    date_created = models.DateField(auto_now_add=True)
    coordinator = models.ForeignKey(User, blank=True, null=True,
        on_delete=models.SET_NULL,
        # Translators: In the administrator interface, this text is help text for a field where staff can specify the username of the account coordinator for this partner.
        help_text=_('The coordinator for this Partner, if any.'))
    featured = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether a publisher will be featured on the website's front page.
        help_text=_("Mark as true to feature this partner on the front page."))
    company_location = CountryField(null=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can enter the partner organisation's country.
        help_text=_("Partner's primary location."))

    # Status metadata
    # --------------------------------------------------------------------------
    # AVAILABLE partners are displayed to users.
    # NOT AVAILABLE partners are only accessible through the admin interface.
    # These may be, e.g., partners TWL used to work with but no longer does
    # (kept in the database for recordkeeping), or they may be partners TWL
    # is setting up a relationship with but isn't ready to expose to public
    # view.
    # We default to NOT_AVAILABLE to avoid inadvertently exposing Partners to
    # the application process when they're not ready yet, and to give staff
    # a chance to build their record incrementally and fix errors.
    AVAILABLE = 0
    NOT_AVAILABLE = 1
    WAITLIST = 2

    STATUS_CHOICES = (
        # Translators: This is a status for a Partner, denoting that editors can apply for access.
        (AVAILABLE, _('Available')),
        # Translators: This is a status for a Partner, denoting that editors cannot apply for access and the Partner will not be displayed to them.
        (NOT_AVAILABLE, _('Not available')),
        # Translators: This is a status for a Partner, denoting that it has no access grants available at this time (but should later).
        (WAITLIST, _('Waitlisted')),
    )

    EMAIL = 0
    CODES = 1
    PROXY = 2
    BUNDLE = 3

    AUTHORIZATION_METHODS = (
        # Translators: This is the name of the authorization method whereby user accounts are set up by email.
        (EMAIL, _('Email')),
        # Translators: This is the name of the authorization method whereby user accounts are set up via an access code.
        (CODES, _('Access codes')),
        # Translators: This is the name of the authorization method whereby users access resources via an IP proxy.
        (PROXY, _('Proxy')),
        # Translators: This is the name of the authorization method whereby users access resources automatically via the library bundle.
        (BUNDLE, _('Library Bundle')),
    )

    status = models.IntegerField(choices=STATUS_CHOICES,
        default=NOT_AVAILABLE,
        # Translators: In the administrator interface, this text is help text for a field where staff can specify whether this partner should be displayed to users.
        help_text=_('Should this Partner be displayed to users? Is it '
                    'open for applications right now?'))

    renewals_available = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a field where staff specify whether users can request their account be renewed/extended for this partner.
        help_text=_('Can access grants to this partner be renewed? If so, '
            'users will be able to request renewals at any time.'))
            
    accounts_available = models.PositiveSmallIntegerField(blank=True, null=True, 
        # Translators: In the administrator interface, this text is help text for a field where staff specify the total number of available accounts.
        help_text=_('Add number of new accounts to the existing value, not by reseting it to zero.'))
    
    # Optional resource metadata
    # --------------------------------------------------------------------------

    terms_of_use = models.URLField(blank=True, null=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can link to a partner's Terms of Use.
        help_text=_("Link to terms of use. Required if users must agree to "
            "terms of use to get access; optional otherwise."))

    short_description = models.TextField(max_length=1000, blank=True, null=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can provide a description of a partner's available resources.
        help_text=_("Optional short description of this partner's resources."))

    description = models.TextField("long description", blank=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can provide a long description of a partner's available resources.
        help_text=_("Optional detailed description in addition to the short "
        "description such as collections, instructions, notes, special "
        "requirements, alternate access options, unique features, citations notes."))
        
    send_instructions = models.TextField(blank=True, null=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can provide instructions to coordinators on sending user data to partners.
        help_text=_("Optional instructions for sending application data to "
            "this partner."))
    
    excerpt_limit = models.PositiveSmallIntegerField(blank=True, null=True,
          # Translators: In the administrator interface, this text is help text for a field where staff can optionally provide a excerpt word limit per article.
          help_text=_("Optional excerpt limit in terms of number of words per article. Leave empty if no limit."))

    excerpt_limit_percentage   = models.PositiveSmallIntegerField(blank=True, null=True, validators = [MaxValueValidator(100)],
          # Translators: In the administrator interface, this text is help text for a field where staff can optionally provide a excerpt word limit per article in terms of percentage per article.
          help_text=_("Optional excerpt limit in terms of percentage (%) of an article. Leave empty if no limit."))

    authorization_method = models.IntegerField(choices=AUTHORIZATION_METHODS,
        default=EMAIL,
        # Translators: In the administrator interface, this text is help text for a field where staff can specify which method of account distribution this partner uses.
        help_text=_("Which authorization method does this partner use? "
            "'Email' means the accounts are set up via email, and is the default. "
            "Select 'Access Codes' if we send individual, or group, login details "
            "or access codes. 'Proxy' means access delivered directly via EZProxy, "
            "and Library Bundle is automated proxy-based access."))

    mutually_exclusive = models.NullBooleanField(
        blank=True, null=True,
        default=None,
        # Translators: In the administrator interface, this text is help text for a field where staff can specify whether users can apply for one or multiple collections of resources. Streams means 'collections'.
        help_text=_("If True, users can only apply for one Stream at a time "
        "from this Partner. If False, users can apply for multiple Streams at "
        "a time. This field must be filled in when Partners have multiple "
        "Streams, but may be left blank otherwise."))

    languages = models.ManyToManyField(Language, blank=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can specify the languages a partner has resources in.
        help_text=_("Select all languages in which this partner publishes "
            "content.")
        )

    tags = TaggableManager(through=TaggedTextField, blank=True)

    # This field has to stick around until all servers are using the new tags.
    old_tags = TaggableManager(through=None, blank=True, verbose_name=_('Old Tags'))

    # Non-universal form fields
    # --------------------------------------------------------------------------

    # Some fields are required by all resources for all access grants.
    # Some fields are only required by some resources. This is where we track
    # whether *this* resource requires those optional fields.

    registration_url = models.URLField(blank=True, null=True,
        # Translators: In the administrator interface, this text is help text for a field where staff can link to a partner's registration page.
        help_text=_("Link to registration page. Required if users must sign up "
            "on the partner's website in advance; optional otherwise."))
    real_name = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must specify their real name when applying
        help_text=_('Mark as true if this partner requires applicant names.'))
    country_of_residence = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must specify the country in which they live when applying.
        help_text=_('Mark as true if this partner requires applicant countries '
                    'of residence.'))
    specific_title = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must specify a title for the resource they want to access when applying.
        help_text=_('Mark as true if this partner requires applicants to '
                    'specify the title they want to access.'))
    specific_stream = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must specify a collection of resources when applying.
        help_text=_('Mark as true if this partner requires applicants to '
                    'specify the database they want to access.'))
    occupation = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must specify their occupation when applying.
        help_text=_('Mark as true if this partner requires applicants to '
                    'specify their occupation.'))
    affiliation = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must specify their institutional affiliation (e.g. university) when applying.
        help_text=_('Mark as true if this partner requires applicants to '
                    'specify their institutional affiliation.'))
    agreement_with_terms_of_use = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must agree to Terms of Use when applying.
        help_text=_("Mark as true if this partner requires applicants to agree "
                    "with the partner's terms of use."))
    account_email = models.BooleanField(default=False,
        # Translators: In the administrator interface, this text is help text for a check box where staff can select whether users must first register at the organisation's website before finishing their application.
        help_text=_("Mark as true if this partner requires applicants to have "
                    "already signed up at the partner website."))


    def __unicode__(self):
        return self.company_name


    def clean(self):
        if self.agreement_with_terms_of_use and not self.terms_of_use:
            raise ValidationError('When agreement with terms of use is '
                'required, a link to terms of use must be provided.')
        if self.streams.count() > 1:
            if self.mutually_exclusive is None:
                raise ValidationError('Since this resource has multiple '
                    'Streams, you must specify a value for mutually_exclusive.')
        if self.account_email and not self.registration_url:
            raise ValidationError('When pre-registration is required, '
                'a link to the registration page must be provided.')


    def get_absolute_url(self):
        return reverse_lazy('partners:detail', kwargs={'pk': self.pk})

    def save(self, *args, **kwargs):
        """Invalidate this partner's pandoc-rendered html from cache"""
        super(Partner, self).save(*args, **kwargs)
        for code in RESOURCE_LANGUAGE_CODES:
          short_description_cache_key = make_template_fragment_key(
              'partner_short_description', [code, self.pk]
          )        
          description_cache_key = make_template_fragment_key(
              'partner_description', [code, self.pk]
          )
          send_instructions_cache_key = make_template_fragment_key(
              'partner_send_instructions', [code, self.pk]
          )
          cache.delete(short_description_cache_key)
          cache.delete(description_cache_key)
          cache.delete(send_instructions_cache_key)

    @property
    def get_languages(self):
        return self.languages.all()


    @property
    def is_waitlisted(self):
        return self.status == self.WAITLIST


    @property
    def is_not_available(self):
        return self.status == self.NOT_AVAILABLE
Beispiel #2
0
    def from_model(cls, model, exclude_rels=False):
        """Given a model, return a ModelState representing it."""
        # Deconstruct the fields
        fields = []
        for field in model._meta.local_fields:
            if getattr(field, "remote_field", None) and exclude_rels:
                continue
            if isinstance(field, OrderWrt):
                continue
            name = field.name
            try:
                fields.append((name, field.clone()))
            except TypeError as e:
                raise TypeError("Couldn't reconstruct field %s on %s: %s" % (
                    name,
                    model._meta.label,
                    e,
                ))
        if not exclude_rels:
            for field in model._meta.local_many_to_many:
                name = field.name
                try:
                    fields.append((name, field.clone()))
                except TypeError as e:
                    raise TypeError(
                        "Couldn't reconstruct m2m field %s on %s: %s" % (
                            name,
                            model._meta.object_name,
                            e,
                        ))
        # Extract the options
        options = {}
        for name in DEFAULT_NAMES:
            # Ignore some special options
            if name in ["apps", "app_label"]:
                continue
            elif name in model._meta.original_attrs:
                if name == "unique_together":
                    ut = model._meta.original_attrs["unique_together"]
                    options[name] = set(normalize_together(ut))
                elif name == "index_together":
                    it = model._meta.original_attrs["index_together"]
                    options[name] = set(normalize_together(it))
                elif name == "indexes":
                    indexes = [idx.clone() for idx in model._meta.indexes]
                    for index in indexes:
                        if not index.name:
                            index.set_name_with_model(model)
                    options['indexes'] = indexes
                else:
                    options[name] = model._meta.original_attrs[name]
        # If we're ignoring relationships, remove all field-listing model
        # options (that option basically just means "make a stub model")
        if exclude_rels:
            for key in [
                    "unique_together", "index_together",
                    "order_with_respect_to"
            ]:
                if key in options:
                    del options[key]
        # Private fields are ignored, so remove options that refer to them.
        elif options.get('order_with_respect_to') in {
                field.name
                for field in model._meta.private_fields
        }:
            del options['order_with_respect_to']

        def flatten_bases(model):
            bases = []
            for base in model.__bases__:
                if hasattr(base, "_meta") and base._meta.abstract:
                    bases.extend(flatten_bases(base))
                else:
                    bases.append(base)
            return bases

        # We can't rely on __mro__ directly because we only want to flatten
        # abstract models and not the whole tree. However by recursing on
        # __bases__ we may end up with duplicates and ordering issues, we
        # therefore discard any duplicates and reorder the bases according
        # to their index in the MRO.
        flattened_bases = sorted(set(flatten_bases(model)),
                                 key=lambda x: model.__mro__.index(x))

        # Make our record
        bases = tuple(
            (base._meta.label_lower if hasattr(base, "_meta") else base)
            for base in flattened_bases)
        # Ensure at least one base inherits from models.Model
        if not any((isinstance(base, str) or issubclass(base, models.Model))
                   for base in bases):
            bases = (models.Model, )

        managers = []
        manager_names = set()
        default_manager_shim = None
        for manager in model._meta.managers:
            if manager.name in manager_names:
                # Skip overridden managers.
                continue
            elif manager.use_in_migrations:
                # Copy managers usable in migrations.
                new_manager = copy.copy(manager)
                new_manager._set_creation_counter()
            elif manager is model._base_manager or manager is model._default_manager:
                # Shim custom managers used as default and base managers.
                new_manager = models.Manager()
                new_manager.model = manager.model
                new_manager.name = manager.name
                if manager is model._default_manager:
                    default_manager_shim = new_manager
            else:
                continue
            manager_names.add(manager.name)
            managers.append((manager.name, new_manager))

        # Ignore a shimmed default manager called objects if it's the only one.
        if managers == [('objects', default_manager_shim)]:
            managers = []

        # Construct the new ModelState
        return cls(
            model._meta.app_label,
            model._meta.object_name,
            fields,
            options,
            bases,
            managers,
        )
class Client(models.Model):

    name = models.CharField(max_length=100, default='', verbose_name=_(u'Name'))
    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL, verbose_name=_(u'Owner'), blank=True,
        null=True, default=None, on_delete=models.SET_NULL, related_name='oidc_clients_set')
    client_type = models.CharField(
        max_length=30,
        choices=CLIENT_TYPE_CHOICES,
        default='confidential',
        verbose_name=_(u'Client Type'),
        help_text=_(u'<b>Confidential</b> clients are capable of maintaining the confidentiality'
                    u' of their credentials. <b>Public</b> clients are incapable.'))
    client_id = models.CharField(max_length=255, unique=True, verbose_name=_(u'Client ID'))
    client_secret = models.CharField(max_length=255, blank=True, verbose_name=_(u'Client SECRET'))
    response_types = models.ManyToManyField(ResponseType)
    jwt_alg = models.CharField(
        max_length=10,
        choices=JWT_ALGS,
        default='RS256',
        verbose_name=_(u'JWT Algorithm'),
        help_text=_(u'Algorithm used to encode ID Tokens.'))
    date_created = models.DateField(auto_now_add=True, verbose_name=_(u'Date Created'))
    website_url = models.CharField(
        max_length=255, blank=True, default='', verbose_name=_(u'Website URL'))
    terms_url = models.CharField(
        max_length=255,
        blank=True,
        default='',
        verbose_name=_(u'Terms URL'),
        help_text=_(u'External reference to the privacy policy of the client.'))
    contact_email = models.CharField(
        max_length=255, blank=True, default='', verbose_name=_(u'Contact Email'))
    logo = models.FileField(
        blank=True, default='', upload_to='oidc_provider/clients', verbose_name=_(u'Logo Image'))
    reuse_consent = models.BooleanField(
        default=True,
        verbose_name=_('Reuse Consent?'),
        help_text=_('If enabled, server will save the user consent given to a specific client, '
                    'so that user won\'t be prompted for the same authorization multiple times.'))
    require_consent = models.BooleanField(
        default=True,
        verbose_name=_('Require Consent?'),
        help_text=_('If disabled, the Server will NEVER ask the user for consent.'))
    _redirect_uris = models.TextField(
        default='', verbose_name=_(u'Redirect URIs'),
        help_text=_(u'Enter each URI on a new line.'))
    _post_logout_redirect_uris = models.TextField(
        blank=True,
        default='',
        verbose_name=_(u'Post Logout Redirect URIs'),
        help_text=_(u'Enter each URI on a new line.'))
    _scope = models.TextField(
        blank=True,
        default='',
        verbose_name=_(u'Scopes'),
        help_text=_('Specifies the authorized scope values for the client app.'))
    backchannel_logout_uri = models.URLField(
        max_length=255,
        blank=True,
        default='',
        verbose_name=_(u'Back-channel logout URI'),
    )

    class Meta:
        verbose_name = _(u'Client')
        verbose_name_plural = _(u'Clients')

    objects = models.Manager()

    def __str__(self):
        return u'{0}'.format(self.name)

    def __unicode__(self):
        return self.__str__()

    def response_type_values(self):
        return (response_type.value for response_type in self.response_types.all())

    def response_type_descriptions(self):
        # return as a list, rather than a generator, so descriptions display correctly in admin
        return [response_type.description for response_type in self.response_types.all()]

    @property
    def redirect_uris(self):
        return self._redirect_uris.splitlines()

    @redirect_uris.setter
    def redirect_uris(self, value):
        self._redirect_uris = '\n'.join(value)

    @property
    def post_logout_redirect_uris(self):
        return self._post_logout_redirect_uris.splitlines()

    @post_logout_redirect_uris.setter
    def post_logout_redirect_uris(self, value):
        self._post_logout_redirect_uris = '\n'.join(value)

    @property
    def scope(self):
        return self._scope.split()

    @scope.setter
    def scope(self, value):
        self._scope = ' '.join(value)

    @property
    def default_redirect_uri(self):
        return self.redirect_uris[0] if self.redirect_uris else ''
Beispiel #4
0
class BaseModel(models.Model):
    objects = models.Manager()

    class Meta:
        abstract = True
class Organization(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 on Organization list page',
                                        default=True)
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True)
    email = models.EmailField('Email address', blank=True)
    twitter_username = models.CharField(max_length=32, blank=True)
    github_username = models.CharField(max_length=32, blank=True)
    github_repos_num = models.PositiveIntegerField(blank=True, null=True)
    github_gists_num = models.PositiveIntegerField(blank=True, null=True)
    homepage = models.URLField(verify_exists=False, blank=True)
    description = models.TextField(blank=True)
    # Location
    address = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=64, blank=True)
    state = models.CharField(max_length=32, blank=True)
    country = models.CharField(max_length=32,
                               blank=True,
                               help_text="Only necessary if outside the U.S.")
    logo = ImageField(upload_to='img/uploads/org_logos',
                      help_text="Resized to fit 200x50 box in template",
                      blank=True,
                      null=True)
    objects = models.Manager()
    live_objects = LiveOrganizationManager()

    class Meta:
        ordering = ('name', )

    def __unicode__(self):
        return u'%s' % self.name

    def save(self, *args, **kwargs):
        # clean up our username fields, just in case
        if self.twitter_username.startswith('@'):
            self.twitter_username = self.twitter_username.strip('@')
        if '/' in self.twitter_username:
            self.twitter_username = self.twitter_username.split('/')[-1]
        if '/' in self.github_username:
            self.github_username = self.github_username.split('/')[-1]
        super(Organization, self).save(*args, **kwargs)

    @models.permalink
    def get_absolute_url(self):
        return ('organization_detail', (), {'slug': self.slug})

    @property
    def location_string_for_static_map(self):
        _locs = []
        for _loc in [self.address, self.city, self.state, self.country]:
            if _loc: _locs.append(_loc)
        return ",".join(_locs).replace(' ', '+')

    @property
    def location_string_city(self):
        _locs = []
        for _loc in [self.city, self.state, self.country]:
            if _loc: _locs.append(_loc)
        return ", ".join(_locs)

    @property
    def sort_letter(self):
        return self.name.replace('The ', '')[:1]

    def get_live_article_set(self):
        return self.article_set.filter(is_live=True,
                                       show_in_lists=True,
                                       pubdate__lte=datetime.now)

    def get_live_person_set(self):
        return self.person_set.filter(is_live=True)

    def get_live_code_set(self):
        return self.code_set.filter(is_live=True)

    def get_live_job_set(self):
        return self.job_set.filter(is_live=True,
                                   listing_start_date__lte=datetime.today,
                                   listing_end_date__gte=datetime.today)
Beispiel #6
0
class Topic(models.Model):
    name = models.CharField(max_length=20)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    objects = models.Manager()
class InsAdmEtp(CompositeImplementation):
    id = models.CharField(primary_key=True, max_length=30)
    cod_anu = models.ForeignKey(AnneeUni,
                                verbose_name=u"Code Annee Universitaire")
    cod_ind = models.ForeignKey(Individu,
                                db_column='COD_IND',
                                related_name="etapes_ied")
    cod_etp = models.CharField(u"Code Etape",
                               max_length=8,
                               null=True,
                               db_column="COD_ETP")
    cod_vrs_vet = models.CharField(u"(COPIED)Numero Version Etape",
                                   max_length=3,
                                   db_column="COD_VRS_VET")
    num_occ_iae = models.CharField(
        u"Numero d'Occurrence Version Etape Choisie",
        max_length=2,
        null=True,
        db_column="NUM_OCC_IAE")
    cod_dip = models.CharField(u"(COPIED)Code Diplome Etablissement",
                               max_length=7,
                               null=True,
                               db_column="COD_DIP")
    cod_vrs_vdi = models.CharField(u"(COPIED)Numero de Version Diplome",
                                   null=True,
                                   db_column="COD_VRS_VDI",
                                   max_length=3)
    cod_cge = models.CharField(u"Code Centre de Gestion",
                               max_length=3,
                               null=True,
                               db_column="COD_CGE")
    dat_cre_iae = models.DateTimeField(u"Date de création de l'IAE",
                                       null=True,
                                       db_column="DAT_CRE_IAE")
    dat_mod_iae = models.DateTimeField(u"Date de modification de l'IAE",
                                       null=True,
                                       db_column="DAT_MOD_IAE")
    nbr_ins_cyc = models.IntegerField(u'Nombre d\'Inscriptions dans le Cycle',
                                      null=True,
                                      db_column="NBR_INS_CYC")
    nbr_ins_etp = models.IntegerField(u"Nombre d'Inscriptions dans l'Etape",
                                      null=True,
                                      db_column="NBR_INS_ETP")
    dat_annul_res_iae = models.DateTimeField(
        u"Date annulation ou résiliation IA",
        null=True,
        db_column="DAT_ANNUL_RES_IAE")
    tem_iae_prm = models.CharField(u"Temoin Etape Premiere ou Seconde",
                                   max_length=1,
                                   null=True,
                                   db_column="TEM_IAE_PRM")
    nbr_ins_dip = models.IntegerField(u"Nombre d'Inscriptions dans le Diplome",
                                      null=True,
                                      db_column="NBR_INS_DIP")
    eta_iae = models.CharField(u"etat de l'inscription",
                               null=True,
                               max_length=1,
                               db_column='ETA_IAE')
    eta_pmt_iae = models.CharField(u"Etat des paiements des droits",
                                   null=True,
                                   max_length=1,
                                   db_column="ETA_PMT_IAE")
    cod_pru = models.CharField(u"Code profil étudiant",
                               null=True,
                               max_length=2,
                               db_column="COD_PRU")

    force_encaissement = models.BooleanField(u"Forcée l'encaissement",
                                             blank=True,
                                             default=False)
    exoneration = models.CharField(u"Exonération",
                                   max_length=1,
                                   blank=True,
                                   null=True,
                                   choices=(('T', 'Total'), ('P', 'Partiel')))
    demi_annee = models.BooleanField(u"Demi année", default=False)

    inscrits = EtapeNonCondiValideManager()
    inscrits_condi = EtapeCondiValideManager()
    objects = models.Manager()
    _exclude_fields = ['force_encaissement', 'exoneration', 'demi_annee']

    class Meta:
        db_table = u"INS_ADM_ETP_COPY"
        verbose_name = u"Etape"
        verbose_name_plural = u"etapes de l'étudiant"
        app_label = 'django_apogee'

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        if not self.pk:
            self.pk = str(self.cod_anu_id) + str(self.cod_ind_id) + str(self.cod_etp) + str(self.cod_vrs_vet) +\
                        str(self.num_occ_iae)
        return super(InsAdmEtp, self).save(force_insert=force_insert,
                                           force_update=force_update,
                                           using=using,
                                           update_fields=update_fields)

    def cod_opi(self):
        return u"%s" % self.cod_ind.cod_ind_opi

    cod_opi.short_description = u"Code opi"

    @property
    def is_reins(self):
        if self.nbr_ins_etp == 1:
            return False
        else:
            return True

    def nom(self):
        return unicode(self.cod_ind.lib_nom_pat_ind)

    nom.short_description = 'Nom'

    def prenom(self):
        return unicode(self.cod_ind.lib_pr1_ind)

    prenom.short_description = 'Prenom'

    def nom_epoux(self):
        return unicode(self.cod_ind.lib_nom_usu_ind or u'')

    prenom.short_description = 'Prenom'

    def cod_etu(self):
        return unicode(self.cod_ind.cod_etu)

    cod_etu.short_description = 'Code étudiant'

    def adresse(self):
        return unicode(self.cod_ind.get_full_adresse(self.cod_anu))

    adresse.short_description = 'Adresse'

    def dico_adresse(self):
        return self.cod_ind.get_dico_adresse(self.cod_anu)

    dico_adresse.short_description = 'Dico adresse'

    def annulation(self):
        etat = self.eta_iae
        if etat == "E":
            return u"En cours"
        elif etat == 'R':
            return u"Résiliée"
        elif etat == 'A':
            return u"Annulée le %s" % self.dat_annul_res_iae
        else:
            return u'<span class="input label label-warning">Dossier détruit Annomalie</span>'

    annulation.short_description = "Etat de l'inscription administrative"
    annulation.allow_tags = True

    def __str__(self):
        return u"%s %s %s" % (self.cod_ind.cod_etu, self.cod_etp, self.cod_anu)

    def resume(self):
        return u"Etape : %s | Année : %s |Centre de gestion %s" % (
            self.cod_etp, self.cod_anu, self.cod_cge)

    resume.short_description = u"Etape"

    def date(self):
        return u"Date de création : %s | Date de modification : %s | Date de résiliation : %s" % (
            self.dat_cre_iae, self.dat_mod_iae if self.dat_mod_iae else u"",
            self.dat_annul_res_iae if self.dat_annul_res_iae else u"")

    date.short_description = u"Dates d'opération"

    def condi(self):

        if self.cod_pru == 'NO':
            return u"Normal"
        elif self.cod_pru == 'AJ':
            return u"Ajac"
        elif self.cod_pru == 'FP':
            return u"Formation permanente"
        else:
            return u"INCONNU"

    condi.short_description = u"Niveau de l'inscription"

    def bloated_query(self):
        cursor = connections['oracle'].cursor()
        query = """select count(*) from ins_adm_etp where cod_ind = '%s' and tem_iae_prm='O' and cod_dip='%s' and cod_vrs_vdi in (
  select cod_vrs_vdi from VERSION_DIPLOME where cod_sis_vdi in (
    select cod_sis_vdi from version_diplome where cod_vrs_vdi = %s and cod_dip = '%s'));""" % (
            self.cod_ind.cod_ind, self.cod_dip, self.cod_vrs_vdi, self.cod_dip)
        try:
            cursor.execute(query)
            result = cursor.fetchone()[0]
        except DatabaseError:
            return None
        return result

    def cod_etp_condi(self):
        cod_etp = self.cod_etp
        if cod_etp[0] == 'L' and cod_etp[1] in ['2', '3']:
            return cod_etp[0] + str(int(cod_etp[1]) - 1) + cod_etp[2:]
        else:
            return None
Beispiel #8
0
class Poke(models.Model):
    poker_user = models.ForeignKey(User, related_name="poker")
    poked_user = models.ForeignKey(User, related_name="poked")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    objects = models.Manager()
Beispiel #9
0
class Admin(models.Model):
    id = models.AutoField(primary_key=True)
    admin = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)
    objects = models.Manager()
Beispiel #10
0
class Proposal(abstract_models.Subscribable):
	is_published = models.BooleanField(default=False, 
		verbose_name=_('is published'))
	score = models.SmallIntegerField(default=0, verbose_name=_('score'))
	title = models.CharField(max_length=256, verbose_name=_('title'))
	summary = models.TextField(verbose_name=_('summary'))
	text = models.TextField(verbose_name=_('text'))
	language = models.CharField(default='en-ca', max_length=5,
		choices=LANGUAGES, verbose_name=_('language'))

	objects = models.Manager()

	# TODO: add (actors) as a many-to-many relationship

	# propogate to creation
	original_user = models.ForeignKey(
		User, related_name='initiated_proposals', 
		verbose_name=_('original author'))
	user = models.ForeignKey(
		User, related_name='proposals_rectently_edited',
		verbose_name=_('last author'))
	proposal_image = models.ImageField(
		upload_to='proposal_avatars',
		default='proposal-images/default.jpg',
		verbose_name=_('issue image'));
	tags = models.ManyToManyField(
		Tag, related_name='proposals', blank=True, null=True,
		verbose_name=_('tags'))
	sectors = models.ManyToManyField(
		Sector, related_name='proposals', blank=True, null=True,
		verbose_name=_('sectors'))


	def get_event_type(self):

		# Proposals must be saved using save(suppress_publish=True).
		# After saving publish(event_type="ISSUE" | "EDIT") should be called 
		# directly.  This is because the proposal is either being created
		# or edited, but we can't infer that at save() time.
		if not hasattr(self, 'event_type'):
			raise ValueError('Proposals must be saved with' 
				'suppress_publish=True, and then manually published using'
				'proposal.publish(event_type="ISSUE" | "EDIT")'
			)

		return self.event_type

	def get_targets(self):
		sector_targets = [s.subscription_id for s in self.sectors.all()]
		tag_targets = [t.subscription_id for t in self.tags.all()]
		targets = sector_targets + tag_targets

		targets.append(self.subscription_id)

		return targets


	def publish(self, event_type):
		self.event_type=event_type
		super(Proposal, self).publish()


	def get_latest(self):
		return ProposalVersion.get_latest(self)

	def __unicode__(self):
		return self.title

	def get_url(self):
		return self.get_url_by_view_name('proposal')

	def get_url_by_view_name(self, view_name):
		url_stub = reverse(view_name, kwargs={'proposal_id': self.pk})
		return url_stub + slugify(self.title)

	def get_question_url(self):
		url_stub = reverse('ask_question', kwargs={'target_id': self.pk})
		return url_stub + slugify(self.title)

	def get_question_list_url(self):
		url_stub = reverse('questions', kwargs={'proposal_id': self.pk})
		return url_stub + slugify(self.title)

	def get_open_discussions_url(self):
		url_stub = reverse('editors_area',
			kwargs={'issue_id': self.pk, 'open_status': 'open'})
		return url_stub + slugify(self.title)

	def get_closed_discussions_url(self):
		url_stub = reverse('editors_area',
			kwargs={'issue_id': self.pk, 'open_status': 'closed'})
		return url_stub + slugify(self.title)

	def get_start_discussion_url(self):
		url_stub = reverse('start_discussion', kwargs={'target_id': self.pk})
		return url_stub + slugify(self.title)

	def get_start_petition_url(self):
		url_stub = reverse('start_petition', kwargs={'target_id': self.pk})
		return url_stub + slugify(self.title)

	def get_petitions_url(self):
		url_stub = reverse('petitions', kwargs={'proposal_id': self.pk})
		return url_stub + slugify(self.title)

	def get_edit_url(self):
		url_stub = reverse('edit', kwargs={'issue_id': self.pk})
		return url_stub + slugify(self.title)
 
	def get_proposal_url(self):
		return self.get_url_by_view_name('proposal')

	class Meta:
		get_latest_by = 'creation_date'
		verbose_name = _('issue')
		verbose_name_plural = _('issues')
Beispiel #11
0
class SaneScanner(InteractiveSource):
    can_compress = False
    is_interactive = True
    source_type = SOURCE_CHOICE_SANE_SCANNER

    device_name = models.CharField(
        max_length=255,
        help_text=_('Device name as returned by the SANE backend.'),
        verbose_name=_('Device name'))
    mode = models.CharField(
        blank=True,
        choices=SCANNER_MODE_CHOICES,
        default=SCANNER_MODE_COLOR,
        help_text=_(
            'Selects the scan mode (e.g., lineart, monochrome, or color). '
            'If this option is not supported by your scanner, leave it blank.'
        ),
        max_length=16,
        verbose_name=_('Mode'))
    resolution = models.PositiveIntegerField(
        blank=True,
        null=True,
        help_text=_(
            'Sets the resolution of the scanned image in DPI (dots per inch). '
            'Typical value is 200. If this option is not supported by your '
            'scanner, leave it blank.'),
        verbose_name=_('Resolution'))
    source = models.CharField(
        blank=True,
        choices=SCANNER_SOURCE_CHOICES,
        help_text=_(
            'Selects the scan source (such as a document-feeder). If this '
            'option is not supported by your scanner, leave it blank.'),
        max_length=32,
        null=True,
        verbose_name=_('Paper source'))
    adf_mode = models.CharField(
        blank=True,
        choices=SCANNER_ADF_MODE_CHOICES,
        help_text=_(
            'Selects the document feeder mode (simplex/duplex). If this '
            'option is not supported by your scanner, leave it blank.'),
        max_length=16,
        verbose_name=_('ADF mode'))

    objects = models.Manager()

    class Meta:
        verbose_name = _('SANE Scanner')
        verbose_name_plural = _('SANE Scanners')

    def clean_up_upload_file(self, upload_file_object):
        pass

    def execute_command(self, arguments):
        command_line = [setting_scanimage_path.value]
        command_line.extend(arguments)

        with TemporaryFile() as stderr_file_object:
            stdout_file_object = TemporaryFile()

            try:
                logger.debug('Scan command line: %s', command_line)
                subprocess.check_call(command_line,
                                      stdout=stdout_file_object,
                                      stderr=stderr_file_object)
            except subprocess.CalledProcessError:
                stderr_file_object.seek(0)
                error_message = stderr_file_object.read()
                logger.error(
                    'Exception while executing scanning command for source:%s ; %s',
                    self, error_message)

                message = _('Error while executing scanning command '
                            '"%(command_line)s"; %(error_message)s') % {
                                'command_line': ' '.join(command_line),
                                'error_message': error_message
                            }
                self.logs.create(message=message)
                raise SourceException(message)
            else:
                stdout_file_object.seek(0)
                return stdout_file_object

    def get_upload_file_object(self, form_data):
        arguments = [
            '-d',
            self.device_name,
            '--format',
            'tiff',
        ]

        if self.resolution:
            arguments.extend(['--resolution', '{}'.format(self.resolution)])

        if self.mode:
            arguments.extend(['--mode', self.mode])

        if self.source:
            arguments.extend(['--source', self.source])

        if self.adf_mode:
            arguments.extend(['--adf-mode', self.adf_mode])

        file_object = self.execute_command(arguments=arguments)

        return SourceUploadedFile(source=self,
                                  file=PseudoFile(file=file_object,
                                                  name='scan {}'.format(
                                                      now())))
Beispiel #12
0
class Preson(models.Model):
    prople = models.Manager()
Beispiel #13
0
class GeneratedCertificate(models.Model):
    """
    Base model for generated certificates
    """
    # Import here instead of top of file since this module gets imported before
    # the course_modes app is loaded, resulting in a Django deprecation warning.
    from course_modes.models import CourseMode

    # Only returns eligible certificates. This should be used in
    # preference to the default `objects` manager in most cases.
    eligible_certificates = EligibleCertificateManager()

    # Normal object manager, which should only be used when ineligible
    # certificates (i.e. new audit certs) should be included in the
    # results. Django requires us to explicitly declare this.
    objects = models.Manager()

    MODES = Choices('verified', 'honor', 'audit', 'professional',
                    'no-id-professional')

    VERIFIED_CERTS_MODES = [CourseMode.VERIFIED, CourseMode.CREDIT_MODE]

    user = models.ForeignKey(User)
    course_id = CourseKeyField(max_length=255, blank=True, default=None)
    verify_uuid = models.CharField(max_length=32,
                                   blank=True,
                                   default='',
                                   db_index=True)
    download_uuid = models.CharField(max_length=32, blank=True, default='')
    download_url = models.CharField(max_length=128, blank=True, default='')
    grade = models.CharField(max_length=5, blank=True, default='')
    key = models.CharField(max_length=32, blank=True, default='')
    distinction = models.BooleanField(default=False)
    status = models.CharField(max_length=32, default='unavailable')
    mode = models.CharField(max_length=32, choices=MODES, default=MODES.honor)
    name = models.CharField(blank=True, max_length=255)
    created_date = models.DateTimeField(auto_now_add=True)
    modified_date = models.DateTimeField(auto_now=True)
    error_reason = models.CharField(max_length=512, blank=True, default='')

    class Meta(object):
        unique_together = (('user', 'course_id'), )
        app_label = "certificates"

    @classmethod
    def certificate_for_student(cls, student, course_id):
        """
        This returns the certificate for a student for a particular course
        or None if no such certificate exits.
        """
        try:
            return cls.objects.get(user=student, course_id=course_id)
        except cls.DoesNotExist:
            pass

        return None

    @classmethod
    def get_unique_statuses(cls, course_key=None, flat=False):
        """
        1 - Return unique statuses as a list of dictionaries containing the following key value pairs
            [
            {'status': 'status value from db', 'count': 'occurrence count of the status'},
            {...},
            ..., ]

        2 - if flat is 'True' then return unique statuses as a list
        3 - if course_key is given then return unique statuses associated with the given course

        :param course_key: Course Key identifier
        :param flat: boolean showing whether to return statuses as a list of values or a list of dictionaries.
        """
        query = cls.objects

        if course_key:
            query = query.filter(course_id=course_key)

        if flat:
            return query.values_list('status', flat=True).distinct()
        else:
            return query.values('status').annotate(count=Count('status'))

    def invalidate(self):
        """
        Invalidate Generated Certificate by  marking it 'unavailable'.

        Following is the list of fields with their defaults
            1 - verify_uuid = '',
            2 - download_uuid = '',
            3 - download_url = '',
            4 - grade = ''
            5 - status = 'unavailable'
        """
        self.verify_uuid = ''
        self.download_uuid = ''
        self.download_url = ''
        self.grade = ''
        self.status = CertificateStatuses.unavailable

        self.save()

    def is_valid(self):
        """
        Return True if certificate is valid else return False.
        """
        return self.status == CertificateStatuses.downloadable

    def save(self, *args, **kwargs):
        """
        After the base save() method finishes, fire the COURSE_CERT_AWARDED
        signal iff we are saving a record of a learner passing the course.
        """
        super(GeneratedCertificate, self).save(*args, **kwargs)
        if CertificateStatuses.is_passing_status(self.status):
            COURSE_CERT_AWARDED.send_robust(
                sender=self.__class__,
                user=self.user,
                course_key=self.course_id,
                mode=self.mode,
                status=self.status,
            )
Beispiel #14
0
class Attribute(models.Model):
    '''
    Putting the **A** in *EAV*. This holds the attributes, or concepts.
    Examples of possible *Attributes*: color, height, weight,
    number of children, number of patients, has fever?, etc...

    Each attribute has a name, and a description, along with a slug that must
    be unique.  If you don't provide a slug, a default slug (derived from
    name), will be created.

    The *required* field is a boolean that indicates whether this EAV attribute
    is required for entities to which it applies. It defaults to *False*.

    .. warning::
       Just like a normal model field that is required, you will not be able
       to save or create any entity object for which this attribute applies,
       without first setting this EAV attribute.

    There are 7 possible values for datatype:

        * int (TYPE_INT)
        * float (TYPE_FLOAT)
        * text (TYPE_TEXT)
        * date (TYPE_DATE)
        * bool (TYPE_BOOLEAN)
        * object (TYPE_OBJECT)
        * enum (TYPE_ENUM)

    Examples:

    >>> Attribute.objects.create(name='Height', datatype=Attribute.TYPE_INT)
    <Attribute: Height (Integer)>

    >>> Attribute.objects.create(name='Color', datatype=Attribute.TYPE_TEXT)
    <Attribute: Color (Text)>

    >>> yes = EnumValue.objects.create(value='yes')
    >>> no = EnumValue.objects.create(value='no')
    >>> unkown = EnumValue.objects.create(value='unkown')
    >>> ynu = EnumGroup.objects.create(name='Yes / No / Unkown')
    >>> ynu.enums.add(yes, no, unkown)
    >>> Attribute.objects.create(name='Has Fever?',
    ...                          datatype=Attribute.TYPE_ENUM,
    ...                          enum_group=ynu)
    <Attribute: Has Fever? (Multiple Choice)>

    .. warning:: Once an Attribute has been used by an entity, you can not
                 change it's datatype.
    '''
    class Meta:
        ordering = ['name']
        unique_together = ('site', 'slug')

    TYPE_TEXT = 'text'
    TYPE_FLOAT = 'float'
    TYPE_INT = 'int'
    TYPE_DATE = 'date'
    TYPE_BOOLEAN = 'bool'
    TYPE_OBJECT = 'object'
    TYPE_ENUM = 'enum'

    DATATYPE_CHOICES = (
        (TYPE_TEXT, _(u"Text")),
        (TYPE_FLOAT, _(u"Float")),
        (TYPE_INT, _(u"Integer")),
        (TYPE_DATE, _(u"Date")),
        (TYPE_BOOLEAN, _(u"True / False")),
        (TYPE_OBJECT, _(u"Django Object")),
        (TYPE_ENUM, _(u"Multiple Choice")),
    )

    name = models.CharField(_(u"name"),
                            max_length=100,
                            help_text=_(u"User-friendly attribute name"))

    site = models.ForeignKey(Site,
                             verbose_name=_(u"site"),
                             default=Site.objects.get_current)

    slug = EavSlugField(_(u"slug"),
                        max_length=50,
                        db_index=True,
                        help_text=_(u"Short unique attribute label"))

    description = models.CharField(_(u"description"),
                                   max_length=256,
                                   blank=True,
                                   null=True,
                                   help_text=_(u"Short description"))

    enum_group = models.ForeignKey(EnumGroup,
                                   verbose_name=_(u"choice group"),
                                   blank=True,
                                   null=True)

    type = models.CharField(_(u"type"), max_length=20, blank=True, null=True)

    @property
    def help_text(self):
        return self.description

    datatype = EavDatatypeField(_(u"data type"),
                                max_length=6,
                                choices=DATATYPE_CHOICES)

    created = models.DateTimeField(_(u"created"),
                                   default=datetime.now,
                                   editable=False)

    modified = models.DateTimeField(_(u"modified"), auto_now=True)

    required = models.BooleanField(_(u"required"), default=False)

    objects = models.Manager()
    on_site = CurrentSiteManager()

    def get_validators(self):
        '''
        Returns the appropriate validator function from :mod:`~eav.validators`
        as a list (of length one) for the datatype.

        .. note::
           The reason it returns it as a list, is eventually we may want this
           method to look elsewhere for additional attribute specific
           validators to return as well as the default, built-in one.
        '''
        DATATYPE_VALIDATORS = {
            'text': validate_text,
            'float': validate_float,
            'int': validate_int,
            'date': validate_date,
            'bool': validate_bool,
            'object': validate_object,
            'enum': validate_enum,
        }

        validation_function = DATATYPE_VALIDATORS[self.datatype]
        return [validation_function]

    def validate_value(self, value):
        '''
        Check *value* against the validators returned by
        :meth:`get_validators` for this attribute.
        '''
        for validator in self.get_validators():
            validator(value)
        if self.datatype == self.TYPE_ENUM:
            if value not in self.enum_group.enums.all():
                raise ValidationError(_(u"%(enum)s is not a valid choice "
                                        u"for %(attr)s") % \
                                       {'enum': value, 'attr': self})

    def save(self, *args, **kwargs):
        '''
        Saves the Attribute and auto-generates a slug field if one wasn't
        provided.
        '''
        if not self.slug:
            self.slug = EavSlugField.create_slug_from_name(self.name)
        self.full_clean()
        super(Attribute, self).save(*args, **kwargs)

    def clean(self):
        '''
        Validates the attribute.  Will raise ``ValidationError`` if
        the attribute's datatype is *TYPE_ENUM* and enum_group is not set,
        or if the attribute is not *TYPE_ENUM* and the enum group is set.
        '''
        if self.datatype == self.TYPE_ENUM and not self.enum_group:
            raise ValidationError(_(
                u"You must set the choice group for multiple choice" \
                u"attributes"))

        if self.datatype != self.TYPE_ENUM and self.enum_group:
            raise ValidationError(_(
                u"You can only assign a choice group to multiple choice " \
                u"attributes"))

    def get_choices(self):
        '''
        Returns a query set of :class:`EnumValue` objects for this attribute.
        Returns None if the datatype of this attribute is not *TYPE_ENUM*.
        '''
        if not self.datatype == Attribute.TYPE_ENUM:
            return None
        return self.enum_group.enums.all()

    def save_value(self, entity, value):
        '''
        Called with *entity*, any django object registered with eav, and
        *value*, the :class:`Value` this attribute for *entity* should
        be set to.

        If a :class:`Value` object for this *entity* and attribute doesn't
        exist, one will be created.

        .. note::
           If *value* is None and a :class:`Value` object exists for this
            Attribute and *entity*, it will delete that :class:`Value` object.
        '''
        ct = ContentType.objects.get_for_model(entity)
        try:
            value_obj = self.value_set.get(entity_ct=ct,
                                           entity_id=entity.pk,
                                           attribute=self)
        except Value.DoesNotExist:
            if value == None or value == '':
                return
            value_obj = Value.objects.create(entity_ct=ct,
                                             entity_id=entity.pk,
                                             attribute=self)
        if value == None or value == '':
            value_obj.delete()
            return

        if value != value_obj.value:
            value_obj.value = value
            value_obj.save()

    def __unicode__(self):
        return u"%s (%s)" % (self.name, self.get_datatype_display())
Beispiel #15
0
class SiteConfig(dd.Model):
    """
    This model has exactly one instance, used to store persistent
    global site parameters.  Application code sees this instance as
    the :attr:`settings.SITE.site_config
    <lino.core.site.Site.site_config>` property.

    .. attribute:: default_build_method

        The default build method to use when rendering printable documents.

        If this field is empty, Lino uses the value found in
        :attr:`lino.core.site.Site.default_build_method`.

    .. attribute:: simulate_today

        A constant user-defined date to be substituted as current
        system date.

        This should be empty except in situations such as *a
        posteriori* data entry in a prototype.

    .. attribute:: site_company

        The organisation who runs this site.  This is used e.g. when
        printing your address in certain documents or reports.  Or
        newly created partners inherit the country of the site owner.

        If no plugin named 'contacts' is intalled, then this is a
        dummy field which always contains `None`.


    .. attribute:: hide_events_before

        If this is not empty, any calendar events before that date are
        being hidden in certain places.

        For example OverdueEvents, EntriesByController, ...

        Injected by :mod:`lino_xl.lib.cal`.
    """
    class Meta(object):
        abstract = dd.is_abstract_model(__name__, 'SiteConfig')
        verbose_name = _("Site configuration")

    objects = SiteConfigManager()
    real_objects = models.Manager()

    default_build_method = BuildMethods.field(
        verbose_name=_("Default build method"), blank=True, null=True)

    simulate_today = models.DateField(_("Simulated date"),
                                      blank=True,
                                      null=True)

    site_company = dd.ForeignKey("contacts.Company",
                                 blank=True,
                                 null=True,
                                 verbose_name=_("Site owner"),
                                 related_name='site_company_sites')

    def __str__(self):
        return force_text(_("Site Parameters"))

    def update(self, **kw):
        """
        Set some field of the SiteConfig object and store it to the
        database.
        """
        # print("20180502 update({})".format(kw))
        for k, v in kw.items():
            if not hasattr(self, k):
                raise Exception("SiteConfig has no attribute %r" % k)
            setattr(self, k, v)
        self.full_clean()
        self.save()

    def save(self, *args, **kw):
        # print("20180502 save() {}".format(dd.obj2str(self, True)))
        super(SiteConfig, self).save(*args, **kw)
        settings.SITE.clear_site_config()
Beispiel #16
0
class Courses(models.Model):
    id = models.AutoField(primary_key=True)
    course_name = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now_add=True)
    objects = models.Manager()
Beispiel #17
0
class Email(models.Model):
    email = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    emailManager = EmailManager()
    objects = models.Manager()
Beispiel #18
0
class InstrumentAlias(CarnaticStyle, data.models.InstrumentAlias):
    fuzzymanager = managers.FuzzySearchManager()
    objects = models.Manager()
Beispiel #19
0
class SubTopic(models.Model):
    name = models.CharField(max_length=20)
    topic = models.ForeignKey(Topic, related_name="parent")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    objects = models.Manager()
Beispiel #20
0
class IMAPEmail(EmailBaseModel):
    source_type = SOURCE_CHOICE_EMAIL_IMAP

    mailbox = models.CharField(
        default=DEFAULT_IMAP_MAILBOX,
        help_text=_('IMAP Mailbox from which to check for messages.'),
        max_length=64,
        verbose_name=_('Mailbox'))
    search_criteria = models.TextField(
        blank=True,
        default=DEFAULT_IMAP_SEARCH_CRITERIA,
        help_text=_('Criteria to use when searching for messages to process. '
                    'Use the format specified in '
                    'https://tools.ietf.org/html/rfc2060.html#section-6.4.4'),
        null=True,
        verbose_name=_('Search criteria'))
    store_commands = models.TextField(
        blank=True,
        default=DEFAULT_IMAP_STORE_COMMANDS,
        help_text=_(
            'IMAP STORE command to execute on messages after they are '
            'processed. One command per line. Use the commands specified in '
            'https://tools.ietf.org/html/rfc2060.html#section-6.4.6 or '
            'the custom commands for your IMAP server.'),
        null=True,
        verbose_name=_('Store commands'))
    execute_expunge = models.BooleanField(
        default=True,
        help_text=_(
            'Execute the IMAP expunge command after processing each email '
            'message.'),
        verbose_name=_('Execute expunge'))
    mailbox_destination = models.CharField(
        blank=True,
        help_text=_(
            'IMAP Mailbox to which processed messages will be copied.'),
        max_length=96,
        null=True,
        verbose_name=_('Destination mailbox'))

    objects = models.Manager()

    class Meta:
        verbose_name = _('IMAP email')
        verbose_name_plural = _('IMAP email')

    # http://www.doughellmann.com/PyMOTW/imaplib/
    def check_source(self, test=False):
        logger.debug(msg='Starting IMAP email fetch')
        logger.debug('host: %s', self.host)
        logger.debug('ssl: %s', self.ssl)

        if self.ssl:
            server = imaplib.IMAP4_SSL(host=self.host, port=self.port)
        else:
            server = imaplib.IMAP4(host=self.host, port=self.port)

        server.login(user=self.username, password=self.password)
        try:
            server.select(mailbox=self.mailbox)
        except Exception as exception:
            raise SourceException('Error selecting mailbox: {}; {}'.format(
                self.mailbox, exception))

        try:
            status, data = server.uid('SEARCH', None,
                                      *self.search_criteria.strip().split())
        except Exception as exception:
            raise SourceException(
                'Error executing search command; {}'.format(exception))

        if data:
            # data is a space separated sequence of message uids
            uids = data[0].split()
            logger.debug('messages count: %s', len(uids))
            logger.debug('message uids: %s', uids)

            for uid in uids:
                logger.debug('message uid: %s', uid)

                try:
                    status, data = server.uid('FETCH', uid, '(RFC822)')
                except Exception as exception:
                    raise SourceException(
                        'Error fetching message uid: {}; {}'.format(
                            uid, exception))

                try:
                    EmailBaseModel.process_message(source=self,
                                                   message_text=data[0][1])
                except Exception as exception:
                    raise SourceException(
                        'Error processing message uid: {}; {}'.format(
                            uid, exception))

                if not test:
                    if self.store_commands:
                        for command in self.store_commands.split('\n'):
                            try:
                                args = [uid]
                                args.extend(command.strip().split(' '))
                                server.uid('STORE', *args)
                            except Exception as exception:
                                raise SourceException(
                                    'Error executing IMAP store command "{}" '
                                    'on message uid {}; {}'.format(
                                        command, uid, exception))

                    if self.mailbox_destination:
                        try:
                            server.uid('COPY', uid, self.mailbox_destination)
                        except Exception as exception:
                            raise SourceException(
                                'Error copying message uid {} to mailbox {}; '
                                '{}'.format(uid, self.mailbox_destination,
                                            exception))

                    if self.execute_expunge:
                        server.expunge()

        server.close()
        server.logout()
class InsAdmEtpInitial(CompositeInitial):
    cod_anu = models.ForeignKey(AnneeUni, max_length=4, db_column="COD_ANU")
    cod_ind = models.ForeignKey(Individu,
                                db_column='COD_IND',
                                primary_key=True,
                                related_name="etapes")
    cod_etp = models.CharField(u"(COPIED)(COPIED)Code Etape",
                               max_length=6,
                               null=True,
                               db_column="COD_ETP")
    cod_vrs_vet = models.CharField(u"(COPIED)Numero Version Etape",
                                   max_length=3,
                                   null=True,
                                   db_column="COD_VRS_VET")
    num_occ_iae = models.CharField(
        u"Numero d'Occurrence Version Etape Choisie",
        max_length=2,
        null=True,
        db_column="NUM_OCC_IAE")
    cod_dip = models.CharField(u"(COPIED)Code Diplome Etablissement",
                               max_length=7,
                               null=True,
                               db_column="COD_DIP")
    cod_cge = models.CharField(u"(COPIED)Code Centre de Gestion",
                               max_length=3,
                               null=True,
                               db_column="COD_CGE")
    dat_cre_iae = models.DateTimeField(u"Date de création de l'IAE",
                                       null=True,
                                       db_column="DAT_CRE_IAE")
    dat_mod_iae = models.DateTimeField(u"Date de modification de l'IAE",
                                       null=True,
                                       db_column="DAT_MOD_IAE")
    nbr_ins_cyc = models.IntegerField(u'Nombre d\'Inscriptions dans le Cycle',
                                      null=True,
                                      db_column="NBR_INS_CYC")
    nbr_ins_etp = models.IntegerField(u"Nombre d'Inscriptions dans l'Etape",
                                      null=True,
                                      db_column="NBR_INS_ETP")
    dat_annul_res_iae = models.DateTimeField(
        u"Date annulation ou résiliation IA",
        null=True,
        db_column="DAT_ANNUL_RES_IAE")
    tem_iae_prm = models.CharField(u"Temoin Etape Premiere ou Seconde",
                                   max_length=1,
                                   null=True,
                                   db_column="TEM_IAE_PRM")
    nbr_ins_dip = models.IntegerField(u"Nombre d'Inscriptions dans le DIP",
                                      null=True,
                                      db_column="NBR_INS_DIP")
    eta_iae = models.CharField(u"etat de l'inscription",
                               null=True,
                               max_length=1,
                               db_column='ETA_IAE')
    eta_pmt_iae = models.CharField(u"Etat des paiements des droits",
                                   null=True,
                                   max_length=1,
                                   db_column="ETA_PMT_IAE")
    cod_pru = models.CharField(u"Code profil étudiant",
                               null=True,
                               max_length=2,
                               db_column="COD_PRU")
    _composite_field = [
        'cod_anu', 'cod_ind', 'cod_etp', 'cod_vrs_vet', 'num_occ_iae'
    ]
    cod_vrs_vdi = models.CharField(u"(COPIED)Numero de Version Diplome",
                                   null=True,
                                   db_column="COD_VRS_VDI",
                                   max_length=3)
    inscrits = EtapeNonCondiValideManagerOracle()
    inscrits_condi = EtapeCondiValideManagerOracle()
    _exclude_fields = ['force_encaissement', 'exoneration', 'demi_annee']
    objects = models.Manager()

    @property
    def is_reins(self):
        if self.nbr_ins_etp == 1:
            return False
        else:
            return True

    def __str__(self):
        return "{} {} {}".format(self.cod_anu.cod_anu, self.cod_ind_id,
                                 self.cod_etp)

    def date(self):
        return u"Date de création : %s | Date de modification : %s | Date de résiliation : %s" % (
            self.dat_cre_iae, self.dat_mod_iae if self.dat_mod_iae else u"",
            self.dat_annul_res_iae if self.dat_annul_res_iae else u"")

    class Meta:
        db_table = u"INS_ADM_ETP"
        app_label = 'django_apogee'
        managed = False
Beispiel #22
0
class EmailBaseModel(IntervalBaseModel):
    """
    POP3 email and IMAP email sources are non-interactive sources that
    periodically fetch emails from an email account using either the POP3 or
    IMAP email protocol. These sources are useful when users need to scan
    documents outside their office, they can photograph a paper document with
    their phones and send the image to a designated email that is setup as a
    Mayan POP3 or IMAP source. Mayan will periodically download the emails
    and process them as Mayan documents.
    """
    host = models.CharField(max_length=128, verbose_name=_('Host'))
    ssl = models.BooleanField(default=True, verbose_name=_('SSL'))
    port = models.PositiveIntegerField(
        blank=True,
        null=True,
        help_text=_(
            'Typical choices are 110 for POP3, 995 for POP3 over SSL, 143 for '
            'IMAP, 993 for IMAP over SSL.'),
        verbose_name=_('Port'))
    username = models.CharField(max_length=96, verbose_name=_('Username'))
    password = models.CharField(max_length=96, verbose_name=_('Password'))
    metadata_attachment_name = models.CharField(
        default=DEFAULT_METADATA_ATTACHMENT_NAME,
        help_text=_(
            'Name of the attachment that will contains the metadata type '
            'names and value pairs to be assigned to the rest of the '
            'downloaded attachments.'),
        max_length=128,
        verbose_name=_('Metadata attachment name'))
    subject_metadata_type = models.ForeignKey(
        blank=True,
        help_text=_(
            'Select a metadata type to store the email\'s subject value. '
            'Must be a valid metadata type for the document type selected '
            'previously.'),
        on_delete=models.CASCADE,
        null=True,
        related_name='email_subject',
        to=MetadataType,
        verbose_name=_('Subject metadata type'))
    from_metadata_type = models.ForeignKey(
        blank=True,
        help_text=_(
            'Select a metadata type to store the email\'s "from" value. '
            'Must be a valid metadata type for the document type selected '
            'previously.'),
        on_delete=models.CASCADE,
        null=True,
        related_name='email_from',
        to=MetadataType,
        verbose_name=_('From metadata type'))
    store_body = models.BooleanField(
        default=True,
        help_text=_('Store the body of the email as a text document.'),
        verbose_name=_('Store email body'))

    objects = models.Manager()

    class Meta:
        verbose_name = _('Email source')
        verbose_name_plural = _('Email sources')

    @staticmethod
    def process_message(source, message_text):
        from flanker import mime

        metadata_dictionary = {}

        message = mime.from_string(force_bytes(message_text))

        if source.from_metadata_type:
            metadata_dictionary[
                source.from_metadata_type.name] = message.headers.get('From')

        if source.subject_metadata_type:
            metadata_dictionary[source.subject_metadata_type.
                                name] = message.headers.get('Subject')

        document_ids, parts_metadata_dictionary = EmailBaseModel._process_message(
            source=source, message=message)

        metadata_dictionary.update(parts_metadata_dictionary)

        if metadata_dictionary:
            for document in Document.objects.filter(id__in=document_ids):
                set_bulk_metadata(document=document,
                                  metadata_dictionary=metadata_dictionary)

    @staticmethod
    def _process_message(source, message):
        counter = 1
        document_ids = []
        metadata_dictionary = {}

        # Messages are tree based, do nested processing of message parts until
        # a message with no children is found, then work out way up.
        if message.parts:
            for part in message.parts:
                part_document_ids, part_metadata_dictionary = EmailBaseModel._process_message(
                    source=source,
                    message=part,
                )

                document_ids.extend(part_document_ids)
                metadata_dictionary.update(part_metadata_dictionary)
        else:
            # Treat inlines as attachments, both are extracted and saved as
            # documents
            if message.is_attachment() or message.is_inline():
                # Reject zero length attachments
                if len(message.body) == 0:
                    return document_ids, metadata_dictionary

                label = message.detected_file_name or 'attachment-{}'.format(
                    counter)
                counter = counter + 1

                with ContentFile(content=message.body,
                                 name=label) as file_object:
                    if label == source.metadata_attachment_name:
                        metadata_dictionary = yaml_load(
                            stream=file_object.read())
                        logger.debug('Got metadata dictionary: %s',
                                     metadata_dictionary)
                    else:
                        documents = source.handle_upload(
                            document_type=source.document_type,
                            file_object=file_object,
                            expand=(source.uncompress ==
                                    SOURCE_UNCOMPRESS_CHOICE_Y))

                        for document in documents:
                            document_ids.append(document.pk)

            else:
                # If it is not an attachment then it should be a body message part.
                # Another option is to use message.is_body()
                if message.detected_content_type == 'text/html':
                    label = 'email_body.html'
                else:
                    label = 'email_body.txt'

                if source.store_body:
                    with ContentFile(content=force_bytes(message.body),
                                     name=label) as file_object:
                        documents = source.handle_upload(
                            document_type=source.document_type,
                            expand=SOURCE_UNCOMPRESS_CHOICE_N,
                            file_object=file_object)

                        for document in documents:
                            document_ids.append(document.pk)

        return document_ids, metadata_dictionary

    def clean(self):
        if self.subject_metadata_type:
            if self.subject_metadata_type.pk not in self.document_type.metadata.values_list(
                    'metadata_type', flat=True):
                raise ValidationError({
                    'subject_metadata_type':
                    _('Subject metadata type "%(metadata_type)s" is not '
                      'valid for the document type: %(document_type)s') % {
                          'metadata_type': self.subject_metadata_type,
                          'document_type': self.document_type
                      }
                })

        if self.from_metadata_type:
            if self.from_metadata_type.pk not in self.document_type.metadata.values_list(
                    'metadata_type', flat=True):
                raise ValidationError({
                    'from_metadata_type':
                    _('"From" metadata type "%(metadata_type)s" is not '
                      'valid for the document type: %(document_type)s') % {
                          'metadata_type': self.from_metadata_type,
                          'document_type': self.document_type
                      }
                })
class Image(models.Model):

    source_url = models.URLField(max_length=400)
    page_url = models.URLField(unique=True, max_length=400)
    thumbnail = models.ImageField(upload_to='thumbs', null=True)
    origin = models.CharField(choices=origins, max_length=2)
    tags = models.ManyToManyField(Tag)
    hash = models.CharField(max_length=576)
    hidden = models.BooleanField(default=False)
    clicks = models.IntegerField(default=0)
    created = models.DateTimeField(default=datetime.datetime.now)

    active = ActiveManager()
    objects = models.Manager()
    
    def __str__(self):
        return self.page_url



    def create_hash(self):
        thumbnail = Imagelib.open(self.thumbnail.path)
        thumbnail = thumbnail.convert('RGB')
        hash = blockhash(thumbnail, 48)
        self.hash = hash
        self.save(update_fields=["hash"])
        return hash

    def create_thumbnail(self, image_url):
        if not self.thumbnail:
            if  not image_url:
                image_url = self.source_url
            headers = {
                'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36',
            }
            for i in range(5):
                try:
                    r = requests.get(image_url, stream=True, headers=headers)
                    r.raw.decode_content = True # handle spurious Content-Encoding
                except requests.exceptions.ConnectionError:
                    print("error loading image url, connection error")
                    break
                if r.status_code != 200 and r.status_code!= 304:
                    print("error loading image url status code: {}".format(r.status_code))
                    time.sleep(2)
                else:
                    break

            if r.status_code != 200 and r.status_code!= 304:
                    print("giving up on this image, final status code: {}".format(r.status_code))
                    return False

            # Create the thumbnail of dimension size
            size = 500, 500
            img = Imagelib.open(r.raw)
            thumb = ImageOps.fit(img, size, Imagelib.ANTIALIAS)
            if thumb.mode == 'LA':
                print('image mode can\'t be LA')
                return False

            # Get the image name from the url
            img_name = os.path.basename(image_url.split('?', 1)[0])


            file_path = os.path.join(djangoSettings.MEDIA_ROOT, "thumb" + img_name)
            thumb.save(file_path, 'JPEG')

            # Save the thumbnail in the media directory, prepend thumb
            self.thumbnail.save(
                img_name,
                File(open(file_path, 'rb')))

            os.remove(file_path)
            return True
Beispiel #24
0
class AbstractBasket(models.Model):
    """
    Basket object
    """
    # Baskets can be anonymously owned - hence this field is nullable.  When a
    # anon user signs in, their two baskets are merged.
    owner = models.ForeignKey(AUTH_USER_MODEL,
                              null=True,
                              related_name='baskets',
                              on_delete=models.CASCADE,
                              verbose_name=_("Owner"))

    # Basket statuses
    # - Frozen is for when a basket is in the process of being submitted
    #   and we need to prevent any changes to it.
    OPEN, MERGED, SAVED, FROZEN, SUBMITTED = ("Open", "Merged", "Saved",
                                              "Frozen", "Submitted")
    STATUS_CHOICES = (
        (OPEN, _("Open - currently active")),
        (MERGED, _("Merged - superceded by another basket")),
        (SAVED, _("Saved - for items to be purchased later")),
        (FROZEN, _("Frozen - the basket cannot be modified")),
        (SUBMITTED, _("Submitted - has been ordered at the checkout")),
    )
    status = models.CharField(_("Status"),
                              max_length=128,
                              default=OPEN,
                              choices=STATUS_CHOICES)

    # A basket can have many vouchers attached to it.  However, it is common
    # for sites to only allow one voucher per basket - this will need to be
    # enforced in the project's codebase.
    vouchers = models.ManyToManyField('voucher.Voucher',
                                      verbose_name=_("Vouchers"),
                                      blank=True)

    date_created = models.DateTimeField(_("Date created"), auto_now_add=True)
    date_merged = models.DateTimeField(_("Date merged"), null=True, blank=True)
    date_submitted = models.DateTimeField(_("Date submitted"),
                                          null=True,
                                          blank=True)

    # Only if a basket is in one of these statuses can it be edited
    editable_statuses = (OPEN, SAVED)

    class Meta:
        abstract = True
        app_label = 'basket'
        verbose_name = _('Basket')
        verbose_name_plural = _('Baskets')

    objects = models.Manager()
    open = OpenBasketManager()
    saved = SavedBasketManager()

    def __init__(self, *args, **kwargs):
        super(AbstractBasket, self).__init__(*args, **kwargs)

        # We keep a cached copy of the basket lines as we refer to them often
        # within the same request cycle.  Also, applying offers will append
        # discount data to the basket lines which isn't persisted to the DB and
        # so we want to avoid reloading them as this would drop the discount
        # information.
        self._lines = None
        self.offer_applications = OfferApplications()

    def __str__(self):
        return _(
            u"%(status)s basket (owner: %(owner)s, lines: %(num_lines)d)") \
            % {'status': self.status,
               'owner': self.owner,
               'num_lines': self.num_lines}

    # ========
    # Strategy
    # ========

    @property
    def has_strategy(self):
        return hasattr(self, '_strategy')

    def _get_strategy(self):
        if not self.has_strategy:
            raise RuntimeError(
                "No strategy class has been assigned to this basket. "
                "This is normally assigned to the incoming request in "
                "oscar.apps.basket.middleware.BasketMiddleware. "
                "Since it is missing, you must be doing something different. "
                "Ensure that a strategy instance is assigned to the basket!")
        return self._strategy

    def _set_strategy(self, strategy):
        self._strategy = strategy

    strategy = property(_get_strategy, _set_strategy)

    def all_lines(self):
        """
        Return a cached set of basket lines.

        This is important for offers as they alter the line models and you
        don't want to reload them from the DB as that information would be
        lost.
        """
        if self.id is None:
            return self.lines.none()
        if self._lines is None:
            self._lines = (self.lines.select_related(
                'product', 'stockrecord').prefetch_related(
                    'attributes',
                    'product__images').order_by(self._meta.pk.name))
        return self._lines

    def is_quantity_allowed(self, qty):
        """
        Test whether the passed quantity of items can be added to the basket
        """
        # We enforce a max threshold to prevent a DOS attack via the offers
        # system.
        basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
        if basket_threshold:
            total_basket_quantity = self.num_items
            max_allowed = basket_threshold - total_basket_quantity
            if qty > max_allowed:
                return False, _(
                    "Due to technical limitations we are not able "
                    "to ship more than %(threshold)d items in one order.") \
                    % {'threshold': basket_threshold}
        return True, None

    # ============
    # Manipulation
    # ============

    def flush(self):
        """
        Remove all lines from basket.
        """
        if self.status == self.FROZEN:
            raise PermissionDenied("A frozen basket cannot be flushed")
        self.lines.all().delete()
        self._lines = None

    def add_product(self, product, quantity=1, options=None):
        """
        Add a product to the basket

        'stock_info' is the price and availability data returned from
        a partner strategy class.

        The 'options' list should contains dicts with keys 'option' and 'value'
        which link the relevant product.Option model and string value
        respectively.

        Returns (line, created).
          line: the matching basket line
          created: whether the line was created or updated

        """
        if options is None:
            options = []
        if not self.id:
            self.save()

        # Ensure that all lines are the same currency
        price_currency = self.currency
        stock_info = self.strategy.fetch_for_product(product)
        if price_currency and stock_info.price.currency != price_currency:
            raise ValueError(
                ("Basket lines must all have the same currency. Proposed "
                 "line has currency %s, while basket has currency %s") %
                (stock_info.price.currency, price_currency))

        if stock_info.stockrecord is None:
            raise ValueError(
                ("Basket lines must all have stock records. Strategy hasn't "
                 "found any stock record for product %s") % product)

        # Line reference is used to distinguish between variations of the same
        # product (eg T-shirts with different personalisations)
        line_ref = self._create_line_reference(product, stock_info.stockrecord,
                                               options)

        # Determine price to store (if one exists).  It is only stored for
        # audit and sometimes caching.
        defaults = {
            'quantity': quantity,
            'price_excl_tax': stock_info.price.excl_tax,
            'price_currency': stock_info.price.currency,
        }
        if stock_info.price.is_tax_known:
            defaults['price_incl_tax'] = stock_info.price.incl_tax

        line, created = self.lines.get_or_create(
            line_reference=line_ref,
            product=product,
            stockrecord=stock_info.stockrecord,
            defaults=defaults)
        if created:
            for option_dict in options:
                line.attributes.create(option=option_dict['option'],
                                       value=option_dict['value'])
        else:
            line.quantity = max(0, line.quantity + quantity)
            line.save()
        self.reset_offer_applications()

        # Returning the line is useful when overriding this method.
        return line, created

    add_product.alters_data = True
    add = add_product

    def applied_offers(self):
        """
        Return a dict of offers successfully applied to the basket.

        This is used to compare offers before and after a basket change to see
        if there is a difference.
        """
        return self.offer_applications.offers

    def reset_offer_applications(self):
        """
        Remove any discounts so they get recalculated
        """
        self.offer_applications = OfferApplications()
        self._lines = None

    def merge_line(self, line, add_quantities=True):
        """
        For transferring a line from another basket to this one.

        This is used with the "Saved" basket functionality.
        """
        try:
            existing_line = self.lines.get(line_reference=line.line_reference)
        except ObjectDoesNotExist:
            # Line does not already exist - reassign its basket
            line.basket = self
            line.save()
        else:
            # Line already exists - assume the max quantity is correct and
            # delete the old
            if add_quantities:
                existing_line.quantity += line.quantity
            else:
                existing_line.quantity = max(existing_line.quantity,
                                             line.quantity)
            existing_line.save()
            line.delete()
        finally:
            self._lines = None

    merge_line.alters_data = True

    def merge(self, basket, add_quantities=True):
        """
        Merges another basket with this one.

        :basket: The basket to merge into this one.
        :add_quantities: Whether to add line quantities when they are merged.
        """
        # Use basket.lines.all instead of all_lines as this function is called
        # before a strategy has been assigned.
        for line_to_merge in basket.lines.all():
            self.merge_line(line_to_merge, add_quantities)
        basket.status = self.MERGED
        basket.date_merged = now()
        basket._lines = None
        basket.save()
        # Ensure all vouchers are moved to the new basket
        for voucher in basket.vouchers.all():
            basket.vouchers.remove(voucher)
            self.vouchers.add(voucher)

    merge.alters_data = True

    def freeze(self):
        """
        Freezes the basket so it cannot be modified.
        """
        self.status = self.FROZEN
        self.save()

    freeze.alters_data = True

    def thaw(self):
        """
        Unfreezes a basket so it can be modified again
        """
        self.status = self.OPEN
        self.save()

    thaw.alters_data = True

    def submit(self):
        """
        Mark this basket as submitted
        """
        self.status = self.SUBMITTED
        self.date_submitted = now()
        self.save()

    submit.alters_data = True

    # Kept for backwards compatibility
    set_as_submitted = submit

    def is_shipping_required(self):
        """
        Test whether the basket contains physical products that require
        shipping.
        """
        for line in self.all_lines():
            if line.product.is_shipping_required:
                return True
        return False

    # =======
    # Helpers
    # =======

    def _create_line_reference(self, product, stockrecord, options):
        """
        Returns a reference string for a line based on the item
        and its options.
        """
        base = '%s_%s' % (product.id, stockrecord.id)
        if not options:
            return base
        repr_options = [{
            'option': repr(option['option']),
            'value': repr(option['value'])
        } for option in options]
        return "%s_%s" % (base, zlib.crc32(repr(repr_options).encode('utf8')))

    def _get_total(self, property):
        """
        For executing a named method on each line of the basket
        and returning the total.
        """
        total = D('0.00')
        for line in self.all_lines():
            try:
                total += getattr(line, property)
            except ObjectDoesNotExist:
                # Handle situation where the product may have been deleted
                pass
            except TypeError:
                # Handle Unavailable products with no known price
                info = self.strategy.fetch_for_product(line.product)
                if info.availability.is_available_to_buy:
                    raise
                pass
        return total

    # ==========
    # Properties
    # ==========

    @property
    def is_empty(self):
        """
        Test if this basket is empty
        """
        return self.id is None or self.num_lines == 0

    @property
    def is_tax_known(self):
        """
        Test if tax values are known for this basket
        """
        return all([line.is_tax_known for line in self.all_lines()])

    @property
    def total_excl_tax(self):
        """
        Return total line price excluding tax
        """
        return self._get_total('line_price_excl_tax_incl_discounts')

    @property
    def total_tax(self):
        """Return total tax for a line"""
        return self._get_total('line_tax')

    @property
    def total_incl_tax(self):
        """
        Return total price inclusive of tax and discounts
        """
        return self._get_total('line_price_incl_tax_incl_discounts')

    @property
    def total_incl_tax_excl_discounts(self):
        """
        Return total price inclusive of tax but exclusive discounts
        """
        return self._get_total('line_price_incl_tax')

    @property
    def total_discount(self):
        return self._get_total('discount_value')

    @property
    def offer_discounts(self):
        """
        Return basket discounts from non-voucher sources.  Does not include
        shipping discounts.
        """
        return self.offer_applications.offer_discounts

    @property
    def voucher_discounts(self):
        """
        Return discounts from vouchers
        """
        return self.offer_applications.voucher_discounts

    @property
    def has_shipping_discounts(self):
        return len(self.shipping_discounts) > 0

    @property
    def shipping_discounts(self):
        """
        Return discounts from vouchers
        """
        return self.offer_applications.shipping_discounts

    @property
    def post_order_actions(self):
        """
        Return discounts from vouchers
        """
        return self.offer_applications.post_order_actions

    @property
    def grouped_voucher_discounts(self):
        """
        Return discounts from vouchers but grouped so that a voucher which
        links to multiple offers is aggregated into one object.
        """
        return self.offer_applications.grouped_voucher_discounts

    @property
    def total_excl_tax_excl_discounts(self):
        """
        Return total price excluding tax and discounts
        """
        return self._get_total('line_price_excl_tax')

    @property
    def num_lines(self):
        """Return number of lines"""
        return self.all_lines().count()

    @property
    def num_items(self):
        """Return number of items"""
        return sum(line.quantity for line in self.lines.all())

    @property
    def num_items_without_discount(self):
        num = 0
        for line in self.all_lines():
            num += line.quantity_without_discount
        return num

    @property
    def num_items_with_discount(self):
        num = 0
        for line in self.all_lines():
            num += line.quantity_with_discount
        return num

    @property
    def time_before_submit(self):
        if not self.date_submitted:
            return None
        return self.date_submitted - self.date_created

    @property
    def time_since_creation(self, test_datetime=None):
        if not test_datetime:
            test_datetime = now()
        return test_datetime - self.date_created

    @property
    def contains_a_voucher(self):
        if not self.id:
            return False
        return self.vouchers.exists()

    @property
    def is_submitted(self):
        return self.status == self.SUBMITTED

    @property
    def can_be_edited(self):
        """
        Test if a basket can be edited
        """
        return self.status in self.editable_statuses

    @property
    def currency(self):
        # Since all lines should have the same currency, return the currency of
        # the first one found.
        for line in self.all_lines():
            return line.price_currency

    # =============
    # Query methods
    # =============

    def contains_voucher(self, code):
        """
        Test whether the basket contains a voucher with a given code
        """
        if self.id is None:
            return False
        try:
            self.vouchers.get(code=code)
        except ObjectDoesNotExist:
            return False
        else:
            return True

    def product_quantity(self, product):
        """
        Return the quantity of a product in the basket

        The basket can contain multiple lines with the same product, but
        different options and stockrecords. Those quantities are summed up.
        """
        matching_lines = self.lines.filter(product=product)
        quantity = matching_lines.aggregate(Sum('quantity'))['quantity__sum']
        return quantity or 0

    def line_quantity(self, product, stockrecord, options=None):
        """
        Return the current quantity of a specific product and options
        """
        ref = self._create_line_reference(product, stockrecord, options)
        try:
            return self.lines.get(line_reference=ref).quantity
        except ObjectDoesNotExist:
            return 0
class Person(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 on People list page',
                                        default=True)
    first_name = models.CharField(max_length=128)
    last_name = models.CharField(max_length=128)
    slug = models.SlugField(unique=True)
    email = models.EmailField('Email address', blank=True)
    twitter_username = models.CharField(max_length=32, blank=True)
    twitter_bio = models.TextField(blank=True)
    twitter_profile_image_url = models.URLField(verify_exists=False,
                                                blank=True)
    github_username = models.CharField(max_length=32, blank=True)
    github_repos_num = models.PositiveIntegerField(blank=True, null=True)
    github_gists_num = models.PositiveIntegerField(blank=True, null=True)
    description = models.TextField('Bio', blank=True)
    organizations = models.ManyToManyField('Organization',
                                           blank=True,
                                           null=True)
    objects = models.Manager()
    live_objects = LivePersonManager()

    class Meta:
        ordering = (
            'last_name',
            'first_name',
        )
        verbose_name_plural = 'People'

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

    def save(self, *args, **kwargs):
        # clean up our username fields, just in case
        if self.twitter_username:
            self.twitter_username = self.twitter_username.strip()
            if self.twitter_username.startswith('@'):
                self.twitter_username = self.twitter_username.strip('@')
            if '/' in self.twitter_username:
                self.twitter_username = self.twitter_username.split('/')[-1]
        if self.github_username:
            self.github_username = self.github_username.strip()
            if '/' in self.github_username:
                self.github_username = self.github_username.split('/')[-1]
        super(Person, self).save(*args, **kwargs)

    def name(self):
        return u'%s %s' % (self.first_name, self.last_name)

    @models.permalink
    def get_absolute_url(self):
        return ('person_detail', (), {'slug': self.slug})

    @property
    def sort_letter(self):
        return self.last_name[:1]

    def get_live_article_set(self):
        return self.article_set.filter(is_live=True,
                                       show_in_lists=True,
                                       pubdate__lte=datetime.now)

    def get_live_article_authored_set(self):
        return self.article_authors.filter(is_live=True,
                                           show_in_lists=True,
                                           pubdate__lte=datetime.now)

    def get_live_organization_set(self):
        return self.organizations.filter(is_live=True)

    def get_live_code_set(self):
        return self.code_set.filter(is_live=True)
Beispiel #26
0
class Message(models.Model):
    mailbox = models.ForeignKey(Mailbox, related_name='messages')
    subject = models.CharField(max_length=255)
    message_id = models.CharField(max_length=255)
    in_reply_to = models.ForeignKey(
        'django_mailbox.Message', 
        related_name='replies',
        blank=True,
        null=True,
    )
    from_header = models.CharField(
        max_length=255,
    )
    to_header = models.TextField()
    outgoing = models.BooleanField(
        default=False,
        blank=True,
    )

    body = models.TextField()

    processed = models.DateTimeField(
        auto_now_add=True
    )
    read = models.DateTimeField(
        default=None,
        blank=True,
        null=True,
    )

    attachments = models.ManyToManyField(
        MessageAttachment,
        blank=True,
    )

    objects = models.Manager()
    unread_messages = UnreadMessageManager()
    incoming_messages = IncomingMessageManager()
    outgoing_messages = OutgoingMessageManager()

    @property
    def address(self):
        """Property allowing one to get the relevant address(es).
        
        In earlier versions of this library, the model had an `address` field
        storing the e-mail address from which a message was received.  During
        later refactorings, it became clear that perhaps storing sent messages
        would also be useful, so the address field was replaced with two
        separate fields.
        
        """
        if self.outgoing:
            return self.to_addresses()
        else:
            return self.from_addresses()

    @property
    def from_address(self):
        return rfc822.parseaddr(self.from_header)[1]

    @property
    def to_addresses(self):
        addresses = []
        for address in self.to_header.split(','):
            addresses.append(
                    rfc822.parseaddr(
                            address
                        )[1]
                )
        return addresses

    def reply(self, message):
        """Sends a message as a reply to this message instance.
        
        Although Django's e-mail processing will set both Message-ID
        and Date upon generating the e-mail message, we will not be able
        to retrieve that information through normal channels, so we must
        pre-set it.

        """
        if self.mailbox.from_email:
            message.from_email = self.mailbox.from_email
        else:
            message.from_email = settings.DEFAULT_FROM_EMAIL
        message.extra_headers['Message-ID'] = make_msgid()
        message.extra_headers['Date'] = formatdate()
        message.extra_headers['In-Reply-To'] = self.message_id
        message.send()
        return self.mailbox.record_outgoing_message(
                email.message_from_string(
                    message.message().as_string()
                )
            )

    def get_text_body(self):
        def get_body_from_message(message):
            body = ''
            if message.is_multipart():
                for part in message.get_payload():
                    if part.get('content-type', '').split(';')[0] == 'text/plain':
                        body = body + get_body_from_message(part)
            else:
                body = body + message.get_payload()
            return body

        return get_body_from_message(
            self.get_email_object()
        )

    def get_email_object(self):
        return email.message_from_string(self.body)

    def delete(self, *args, **kwargs):
        for attachment in self.attachments.all():
            if attachment.message_set.count() == 1:
                # This attachment is attached only to this message.
                attachment.delete()
        return super(Message, self).delete(*args, **kwargs)

    def __unicode__(self):
        return self.subject
Beispiel #27
0
class Newsletter(models.Model):
    site = models.ManyToManyField(Site, default=get_default_sites)

    title = models.CharField(max_length=200,
                             verbose_name=_('newsletter title'))
    slug = models.SlugField(db_index=True, unique=True)

    email = models.EmailField(verbose_name=_('e-mail'),
                              help_text=_('Sender e-mail'))
    sender = models.CharField(max_length=200,
                              verbose_name=_('sender'),
                              help_text=_('Sender name'))

    visible = models.BooleanField(default=True,
                                  verbose_name=_('visible'),
                                  db_index=True)

    send_html = models.BooleanField(
        default=True,
        verbose_name=_('send html'),
        help_text=_('Whether or not to send HTML versions of e-mails.'))

    objects = models.Manager()

    # Automatically filter the current site
    on_site = CurrentSiteManager()

    def get_templates(self, action):
        """
        Return a subject, text, HTML tuple with e-mail templates for
        a particular action. Returns a tuple with subject, text and e-mail
        template.
        """

        assert action in ACTIONS + ('message', ), 'Unknown action: %s' % action

        # Common substitutions for filenames
        tpl_subst = {'action': action, 'newsletter': self.slug}

        # Common root path for all the templates
        tpl_root = 'newsletter/message/'

        subject_template = select_template([
            tpl_root + '%(newsletter)s/%(action)s_subject.txt' % tpl_subst,
            tpl_root + '%(action)s_subject.txt' % tpl_subst,
        ])

        text_template = select_template([
            tpl_root + '%(newsletter)s/%(action)s.txt' % tpl_subst,
            tpl_root + '%(action)s.txt' % tpl_subst,
        ])

        if self.send_html:
            html_template = select_template([
                tpl_root + '%(newsletter)s/%(action)s.html' % tpl_subst,
                tpl_root + '%(action)s.html' % tpl_subst,
            ])
        else:
            # HTML templates are not required
            html_template = None

        return (subject_template, text_template, html_template)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = _('newsletter')
        verbose_name_plural = _('newsletters')

    @permalink
    def get_absolute_url(self):
        return ('newsletter_detail', (), {'newsletter_slug': self.slug})

    @permalink
    def subscribe_url(self):
        return ('newsletter_subscribe_request', (), {
            'newsletter_slug': self.slug
        })

    @permalink
    def unsubscribe_url(self):
        return ('newsletter_unsubscribe_request', (), {
            'newsletter_slug': self.slug
        })

    @permalink
    def update_url(self):
        return ('newsletter_update_request', (), {
            'newsletter_slug': self.slug
        })

    @permalink
    def archive_url(self):
        return ('newsletter_archive', (), {'newsletter_slug': self.slug})

    def get_sender(self):
        return u'%s <%s>' % (self.sender, self.email)

    def get_subscriptions(self):
        logger.debug(u'Looking up subscribers for %s', self)

        return Subscription.objects.filter(newsletter=self, subscribed=True)

    @classmethod
    def get_default(cls):
        try:
            return cls.objects.all()[0]
        except IndexError:
            return None
Beispiel #28
0
class Mailbox(models.Model):
    name = models.CharField(max_length=255)
    uri = models.CharField(
            max_length=255,
            help_text="""
                Example: imap+ssl://myusername:mypassword@someserver
                <br />
                <br />
                Internet transports include 'imap' and 'pop3'; common local file transports include 'maildir', 'mbox', and less commonly 'babyl', 'mh', and 'mmdf'.
                <br />
                <br />
                Be sure to urlencode your username and password should they 
                contain illegal characters (like @, :, etc).
                """,
            blank=True,
            null=True,
            default=None,
            )
    from_email = models.CharField(
            max_length=255,
            help_text="""
                Example: MailBot &lt;[email protected]&gt;
                <br />
                'From' header to set for outgoing email.
                <br />
                <br />
                If you do not use this e-mail inbox for outgoing mail, this 
                setting is unnecessary.
                <br />
                If you send e-mail without setting this, your 'From' header will
                be set to match the setting `DEFAULT_FROM_EMAIL`.
            """,
            blank=True,
            null=True,
            default=None,
            )
    active = models.BooleanField(
            help_text="""
                Check this e-mail inbox for new e-mail messages during polling
                cycles.  This checkbox does not have an effect upon whether
                mail is collected here when this mailbox receives mail from a
                pipe, and does not affect whether e-mail messages can be
                dispatched from this mailbox.
            """,
            blank=True,
            default=True,
            )

    objects = models.Manager()
    active_mailboxes = ActiveMailboxManager()

    @property
    def _protocol_info(self):
        return urlparse.urlparse(self.uri)

    @property
    def _domain(self):
        return self._protocol_info.hostname

    @property
    def port(self):
        return self._protocol_info.port

    @property
    def username(self):
        return urllib.unquote(self._protocol_info.username)

    @property
    def password(self):
        return urllib.unquote(self._protocol_info.password)

    @property
    def location(self):
        return self._domain if self._domain else '' + self._protocol_info.path

    @property
    def type(self):
        scheme = self._protocol_info.scheme.lower()
        if '+' in scheme:
            return scheme.split('+')[0]
        return scheme

    @property
    def use_ssl(self):
        return '+ssl' in self._protocol_info.scheme.lower()

    def get_connection(self):
        if not self.uri:
            return None
        elif self.type == 'imap':
            conn = ImapTransport(
                        self.location,
                        port=self.port if self.port else None,
                        ssl=self.use_ssl
                    )
            conn.connect(self.username, self.password)
        elif self.type == 'pop3':
            conn = Pop3Transport(
                        self.location,
                        port=self.port if self.port else None,
                        ssl=self.use_ssl
                    )
            conn.connect(self.username, self.password)
        elif self.type == 'maildir':
            conn = MaildirTransport(self.location)
        elif self.type == 'mbox':
            conn = MboxTransport(self.location)
        elif self.type == 'babyl':
            conn = BabylTransport(self.location)
        elif self.type == 'mh':
            conn = MHTransport(self.location)
        elif self.type == 'mmdf':
            conn = MMDFTransport(self.location)
        return conn

    def process_incoming_message(self, message):
        msg = self._process_message(message)
        msg.outgoing = False
        msg.save()
        message_received.send(sender=self, message=msg)
        return msg

    def record_outgoing_message(self, message):
        msg = self._process_message(message)
        msg.outgoing = True
        msg.save()
        return msg

    def _filter_message_body(self, message):
        if not message.is_multipart() or not STRIP_UNALLOWED_MIMETYPES:
            return message
        stripped_content = {}
        new = EmailMessage()
        for header, value in message.items():
            new[header] = value
        for part in message.walk():
            content_type = part.get_content_type()
            print content_type
            if not content_type in ALLOWED_MIMETYPES:
                if content_type not in stripped_content:
                    stripped_content[content_type] = 0
                stripped_content[content_type] = (
                    stripped_content[content_type] + 1
                )
                continue
            new.attach(part)
        new[ALTERED_MESSAGE_HEADER] = 'Stripped ' + ', '.join(
            ['%s*%s' % (key, value) for key, value in stripped_content.items()]
        )
        return new

    def _process_message(self, message):
        msg = Message()
        msg.mailbox = self
        msg.subject = message['subject'][0:255]
        msg.message_id = message['message-id'][0:255]
        msg.from_header = message['from']
        msg.to_header = message['to']
        message = self._filter_message_body(message)
        msg.body = message.as_string()
        if message['in-reply-to']:
            try:
                msg.in_reply_to = Message.objects.filter(message_id=message['in-reply-to'])[0]
            except IndexError:
                pass
        msg.save()
        if message.is_multipart():
            for part in message.walk():
                if part.get_content_maintype() == 'multipart':
                    continue
                if part.get('Content-Disposition') is None:
                    continue
                filename = part.get_filename()
                # ignore SMIME extension
                filename_basename, filename_extension = os.path.splitext(filename)
                buffer_space = 40
                if len(filename) > 100 - buffer_space:
                    # Ensure that there're at least a few chars available afterward
                    # for duplication things like _1, _2 ... _99 and the FileField's
                    # upload_to path.
                    filename_basename = filename_basename[0:100-len(filename_extension)-buffer_space]
                    filename = filename_basename + filename_extension
                if filename_extension in SKIPPED_EXTENSIONS:
                    continue
                data = part.get_payload(decode=True)
                if not data:
                    continue
                temp_file = NamedTemporaryFile(delete=True)
                temp_file.write(data)
                temp_file.flush()
                attachment = MessageAttachment()
                attachment.document.save(filename, File(temp_file))
                attachment.save()
                msg.attachments.add(attachment)
        return msg

    def get_new_mail(self):
        new_mail = []
        connection = self.get_connection()
        if not connection:
            return new_mail
        for message in connection.get_message():
            msg = self.process_incoming_message(message)
            new_mail.append(msg)
        return new_mail

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name_plural = "Mailboxes"
Beispiel #29
0
class Event(models.Model):
    name = models.CharField(max_length=200, null=False, blank=False)
    date = ApproximateDateField(null=True,
                                blank=False,
                                validators=[validate_approximatedate])
    city = models.CharField(max_length=200, null=False, blank=False)
    country = models.CharField(max_length=200, null=False, blank=False)
    latlng = models.CharField(max_length=30, null=True, blank=True)
    photo = models.ImageField(upload_to="event/cities/",
                              null=True,
                              blank=True,
                              help_text="The best would be 356 x 210px")
    photo_credit = models.CharField(max_length=200, null=True, blank=True)
    photo_link = models.URLField(null=True, blank=True)
    email = models.EmailField(max_length=75, null=True, blank=True)
    main_organizer = models.ForeignKey(User,
                                       null=True,
                                       blank=True,
                                       related_name="main_organizer")
    team = models.ManyToManyField(User, blank=True)
    is_on_homepage = models.BooleanField(default=False)
    is_deleted = models.BooleanField(default=False)

    objects = EventManager()
    all_objects = models.Manager()  # This includes deleted objects

    def __str__(self):
        return self.name

    class Meta:
        ordering = ('pk', )
        verbose_name_plural = "List of events"

    def is_upcoming(self):
        now = timezone.now()
        now = ApproximateDate(year=now.year, month=now.month, day=now.day)
        if now < self.date:
            return True
        return False

    @property
    def ical_uid(self):
        return "*****@*****.**" % self.pk

    def as_ical(self):
        """
        Return a representation of the current event as an icalendar.Event.
        """
        if not self.date:
            return None
        ymd = (self.date.year, self.date.month, self.date.day)
        if not all(ymd):
            return None
        event_date = date(*ymd)
        event = icalendar.Event()
        event.add("dtstart", event_date)
        event.add("dtend", event_date + timedelta(days=1))
        event.add("uid", self.ical_uid)
        event.add("summary", "Django Girls %s" % self.city)
        event.add("location", "%s, %s" % (self.country, self.city))
        return event

    def organizers(self):
        members = [
            "{} <{}>".format(x.get_full_name(), x.email)
            for x in self.team.all()
        ]
        return ", ".join(members)

    def delete(self):
        self.is_deleted = True
        self.save()
Beispiel #30
0
class MenuItem(MPTTModel, SortableModel):
    menu = models.ForeignKey(Menu,
                             related_name='items',
                             on_delete=models.CASCADE)
    name = models.CharField(max_length=128)
    parent = models.ForeignKey('self',
                               null=True,
                               blank=True,
                               related_name='children',
                               on_delete=models.CASCADE)

    # not mandatory fields, usage depends on what type of link is stored
    url = models.URLField(max_length=256, blank=True, null=True)
    category = models.ForeignKey(Category,
                                 blank=True,
                                 null=True,
                                 on_delete=models.CASCADE)
    collection = models.ForeignKey(Collection,
                                   blank=True,
                                   null=True,
                                   on_delete=models.CASCADE)
    page = models.ForeignKey(Page,
                             blank=True,
                             null=True,
                             on_delete=models.CASCADE)

    objects = models.Manager()
    tree = TreeManager()
    translated = TranslationProxy()

    class Meta:
        ordering = ('sort_order', )
        app_label = 'menu'

    def __str__(self):
        return self.name

    def get_ordering_queryset(self):
        return (self.menu.items.all()
                if not self.parent else self.parent.children.all())

    @property
    def linked_object(self):
        return self.category or self.collection or self.page

    @property
    def destination_display(self):
        linked_object = self.linked_object

        if not linked_object:
            prefix = pgettext_lazy('Link object type description', 'URL: ')
            return prefix + self.url

        if isinstance(linked_object, Category):
            prefix = pgettext_lazy('Link object type description',
                                   'Category: ')
        elif isinstance(linked_object, Collection):
            prefix = pgettext_lazy('Link object type description',
                                   'Collection: ')
        else:
            prefix = pgettext_lazy('Link object type description', 'Page: ')

        return prefix + str(linked_object)

    def get_url(self):
        linked_object = self.linked_object
        return linked_object.get_absolute_url() if linked_object else self.url

    def is_public(self):
        return not self.linked_object or getattr(self.linked_object,
                                                 'is_published', True)