示例#1
0
 def test_default_choices_not_frozen(self):
     """
     Ensure the deconstructed representation of the field does not contain
     kwargs if they match the default.
     Don't want to bloat everyone's migration files.
     """
     field = TimeZoneField()
     name, path, args, kwargs = field.deconstruct()
     self.assertNotIn('choices', kwargs)
     self.assertNotIn('max_length', kwargs)
示例#2
0
    def test_from_db_value(self):
        """
        Verify that the field can handle data coming back as bytes from the
        db.
        """
        field = TimeZoneField()

        # django 1.11 signuature
        value = field.from_db_value(b'UTC', None, None, None)
        self.assertEqual(pytz.UTC, value)

        # django 2.0+ signuature
        value = field.from_db_value(b'UTC', None, None)
        self.assertEqual(pytz.UTC, value)
示例#3
0
    def test_specifying_defaults_not_frozen(self):
        """
        If someone's matched the default values with their kwarg args, we
        shouldn't bothering freezing those.
        """
        field = TimeZoneField(max_length=63)
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('max_length', kwargs)

        choices = [(pytz.timezone(tz), tz) for tz in pytz.common_timezones]
        field = TimeZoneField(choices=choices)
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('choices', kwargs)

        choices = [(tz, tz) for tz in pytz.common_timezones]
        field = TimeZoneField(choices=choices)
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('choices', kwargs)
示例#4
0
class Event(models.Model):
    objects = EventManager()
    short = models.CharField(max_length=64, unique=True)
    name = models.CharField(max_length=128)
    receivername = models.CharField(max_length=128,
                                    blank=True,
                                    null=False,
                                    verbose_name='Receiver Name')
    targetamount = models.DecimalField(decimal_places=2,
                                       max_digits=20,
                                       validators=[positive, nonzero],
                                       verbose_name='Target Amount')
    minimumdonation = models.DecimalField(
        decimal_places=2,
        max_digits=20,
        validators=[positive, nonzero],
        verbose_name='Minimum Donation',
        help_text='Enforces a minimum donation amount on the donate page.',
        default=decimal.Decimal('1.00'))
    usepaypalsandbox = models.BooleanField(default=False,
                                           verbose_name='Use Paypal Sandbox')
    paypalemail = models.EmailField(max_length=128,
                                    null=False,
                                    blank=False,
                                    verbose_name='Receiver Paypal')
    paypalcurrency = models.CharField(max_length=8,
                                      null=False,
                                      blank=False,
                                      default=_currencyChoices[0][0],
                                      choices=_currencyChoices,
                                      verbose_name='Currency')
    donationemailtemplate = models.ForeignKey(
        post_office.models.EmailTemplate,
        verbose_name='Donation Email Template',
        default=None,
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name='event_donation_templates')
    pendingdonationemailtemplate = models.ForeignKey(
        post_office.models.EmailTemplate,
        verbose_name='Pending Donation Email Template',
        default=None,
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name='event_pending_donation_templates')
    donationemailsender = models.EmailField(
        max_length=128,
        null=True,
        blank=True,
        verbose_name='Donation Email Sender')
    scheduleid = models.CharField(max_length=128,
                                  unique=True,
                                  null=True,
                                  blank=True,
                                  verbose_name='Schedule ID')
    scheduletimezone = models.CharField(max_length=64,
                                        blank=True,
                                        choices=_timezoneChoices,
                                        default='US/Eastern',
                                        verbose_name='Schedule Timezone')
    scheduledatetimefield = models.CharField(max_length=128,
                                             blank=True,
                                             verbose_name='Schedule Datetime')
    schedulegamefield = models.CharField(max_length=128,
                                         blank=True,
                                         verbose_name='Schdule Game')
    schedulerunnersfield = models.CharField(max_length=128,
                                            blank=True,
                                            verbose_name='Schedule Runners')
    scheduleestimatefield = models.CharField(max_length=128,
                                             blank=True,
                                             verbose_name='Schedule Estimate')
    schedulesetupfield = models.CharField(max_length=128,
                                          blank=True,
                                          verbose_name='Schedule Setup')
    schedulecommentatorsfield = models.CharField(
        max_length=128, blank=True, verbose_name='Schedule Commentators')
    schedulecommentsfield = models.CharField(max_length=128,
                                             blank=True,
                                             verbose_name='Schedule Comments')
    date = models.DateField()
    timezone = TimeZoneField(default='US/Eastern')
    locked = models.BooleanField(
        default=False,
        help_text=
        'Requires special permission to edit this event or anything associated with it'
    )
    # Fields related to prize management
    prizecoordinator = models.ForeignKey(
        User,
        default=None,
        null=True,
        blank=True,
        verbose_name='Prize Coordinator',
        help_text=
        'The person responsible for managing prize acceptance/distribution')
    allowed_prize_countries = models.ManyToManyField(
        'Country',
        blank=True,
        verbose_name="Allowed Prize Countries",
        help_text=
        "List of countries whose residents are allowed to receive prizes (leave blank to allow all countries)"
    )
    disallowed_prize_regions = models.ManyToManyField(
        'CountryRegion',
        blank=True,
        verbose_name='Disallowed Regions',
        help_text=
        'A blacklist of regions within allowed countries that are not allowed for drawings (e.g. Quebec in Canada)'
    )
    prize_accept_deadline_delta = models.IntegerField(
        default=14,
        null=False,
        blank=False,
        verbose_name='Prize Accept Deadline Delta',
        help_text=
        'The number of days a winner will be given to accept a prize before it is re-rolled.',
        validators=[positive, nonzero])
    prizecontributoremailtemplate = models.ForeignKey(
        post_office.models.EmailTemplate,
        default=None,
        null=True,
        blank=True,
        verbose_name='Prize Contributor Accept/Deny Email Template',
        help_text=
        "Email template to use when responding to prize contributor's submission requests",
        related_name='event_prizecontributortemplates')
    prizewinneremailtemplate = models.ForeignKey(
        post_office.models.EmailTemplate,
        default=None,
        null=True,
        blank=True,
        verbose_name='Prize Winner Email Template',
        help_text="Email template to use when someone wins a prize.",
        related_name='event_prizewinnertemplates')
    prizewinneracceptemailtemplate = models.ForeignKey(
        post_office.models.EmailTemplate,
        default=None,
        null=True,
        blank=True,
        verbose_name='Prize Accepted Email Template',
        help_text=
        "Email template to use when someone accepts a prize (and thus it needs to be shipped).",
        related_name='event_prizewinneraccepttemplates')
    prizeshippedemailtemplate = models.ForeignKey(
        post_office.models.EmailTemplate,
        default=None,
        null=True,
        blank=True,
        verbose_name='Prize Shipped Email Template',
        help_text=
        "Email template to use when the aprize has been shipped to its recipient).",
        related_name='event_prizeshippedtemplates')

    def __unicode__(self):
        return self.name

    def natural_key(self):
        return (self.short, )

    def clean(self):
        if self.id and self.id < 1:
            raise ValidationError('Event ID must be positive and non-zero')
        if not re.match('^\w+$', self.short):
            raise ValidationError('Event short name must be a url-safe string')
        if not self.scheduleid:
            self.scheduleid = None
        if self.donationemailtemplate != None or self.pendingdonationemailtemplate != None:
            if not self.donationemailsender:
                raise ValidationError(
                    'Must specify a donation email sender if automailing is used'
                )

    def start_push_notification(self, request):
        from django.core.urlresolvers import reverse
        approval_force = False
        try:
            credentials = CredentialsModel.objects.get(
                id=request.user).credentials
            if credentials:
                if not credentials.refresh_token:
                    approval_force = True
                    raise CredentialsModel.DoesNotExist
                elif credentials.access_token_expired:
                    import httplib2
                    credentials.refresh(httplib2.Http())
        except CredentialsModel.DoesNotExist:
            from django.conf import settings
            from django.http import HttpResponseRedirect
            FlowModel.objects.filter(id=request.user).delete()
            kwargs = {}
            if approval_force:
                kwargs['approval_prompt'] = 'force'
            defaultflow = OAuth2WebServerFlow(
                client_id=settings.GOOGLE_CLIENT_ID,
                client_secret=settings.GOOGLE_CLIENT_SECRET,
                scope='https://www.googleapis.com/auth/drive.metadata.readonly',
                redirect_uri=request.build_absolute_uri(
                    reverse('admin:google_flow')).replace(
                        '/cutler5:', '/cutler5.example.com:'),
                access_type='offline',
                **kwargs)
            flow = FlowModel(id=request.user, flow=defaultflow)
            flow.save()
            url = flow.flow.step1_get_authorize_url()
            return HttpResponseRedirect(url)
        from apiclient.discovery import build
        import httplib2
        import uuid
        import time
        drive = build('drive', 'v2', credentials.authorize(httplib2.Http()))
        body = {
            'kind':
            'api#channel',
            'resourceId':
            self.scheduleid,
            'id':
            unicode(uuid.uuid4()),
            'token':
            u'%s:%s' % (self.id, unicode(request.user)),
            'type':
            'web_hook',
            'address':
            request.build_absolute_uri(
                reverse('tracker.views.refresh_schedule')),
            'expiration':
            int(time.time() + 24 * 60 * 60) * 1000  # approx one day
        }
        try:
            drive.files().watch(fileId=self.scheduleid, body=body).execute()
        except Exception as e:
            from django.contrib import messages

            messages.error(request,
                           u'Could not start push notification: %s' % e)
            return False
        return True

    class Meta:
        app_label = 'tracker'
        get_latest_by = 'date'
        permissions = (('can_edit_locked_events', 'Can edit locked events'), )
        ordering = ('date', )
示例#5
0
class MemberProfile(index.Indexed, ClusterableModel):
    """
    Contains additional comses.net information, possibly linked to a CoMSES Member / site account
    """

    user = models.OneToOneField(User,
                                null=True,
                                on_delete=models.SET_NULL,
                                related_name='member_profile')

    # FIXME: add location field eventually, with postgis
    # location = LocationField(based_fields=['city'], zoom=7)

    timezone = TimeZoneField(blank=True)

    affiliations = JSONField(
        default=list, help_text=_("JSON-LD list of affiliated institutions"))
    bio = MarkdownField(max_length=2048, help_text=_('Brief bio'))
    degrees = ArrayField(models.CharField(max_length=255),
                         blank=True,
                         default=list)
    institution = models.ForeignKey(Institution,
                                    null=True,
                                    on_delete=models.SET_NULL)
    tags = ClusterTaggableManager(through=MemberProfileTag, blank=True)

    personal_url = models.URLField(blank=True)
    picture = models.ForeignKey(Image,
                                null=True,
                                help_text=_('Profile picture'),
                                on_delete=models.SET_NULL)
    professional_url = models.URLField(blank=True)
    research_interests = MarkdownField(max_length=2048)

    objects = MemberProfileQuerySet.as_manager()

    panels = [
        FieldPanel('bio', widget=forms.Textarea),
        FieldPanel('research_interests', widget=forms.Textarea),
        FieldPanel('personal_url'),
        FieldPanel('professional_url'),
        FieldPanel('institution'),
        ImageChooserPanel('picture'),
        FieldPanel('tags'),
    ]

    search_fields = [
        index.SearchField('bio', partial_match=True, boost=5),
        index.SearchField('research_interests', partial_match=True, boost=5),
        index.FilterField('is_active'),
        index.FilterField('username'),
        index.SearchField('degrees', partial_match=True),
        index.SearchField('name', partial_match=True, boost=5),
        index.RelatedFields('institution', [
            index.SearchField('name', partial_match=True),
        ]),
        index.RelatedFields('tags', [
            index.SearchField('name', partial_match=True),
        ]),
        index.RelatedFields('user', [
            index.SearchField('first_name', partial_match=True),
            index.SearchField('last_name', partial_match=True, boost=3),
            index.SearchField('email', partial_match=True, boost=3),
            index.SearchField('username', partial_match=True, boost=5),
        ]),
    ]
    """
    Returns the ORCID profile URL associated with this member profile if it exists, or None
    """
    @property
    def orcid_url(self):
        return self.get_social_account_profile_url('orcid')

    @property
    def avatar_url(self):
        if self.picture:
            return self.picture.get_rendition('fill-150x150').url
        return None

    """
    Returns the github profile URL associated with this member profile if it exists, or None
    """

    # Proxies to related user object

    @property
    def date_joined(self):
        return self.user.date_joined

    @property
    def email(self):
        return self.user.email

    @property
    def username(self):
        return self.user.username

    @property
    def is_active(self):
        return self.user.is_active

    # Urls

    @property
    def github_url(self):
        return self.get_social_account_profile_url('github')

    def get_social_account_profile_url(self, provider_name):
        social_acct = self.get_social_account(provider_name)
        if social_acct:
            return social_acct.get_profile_url()
        return None

    def get_social_account(self, provider_name):
        return self.user.socialaccount_set.filter(
            provider=provider_name).first()

    @property
    def institution_url(self):
        return self.institution.url if self.institution else ''

    @property
    def profile_url(self):
        return self.get_absolute_url()

    def get_absolute_url(self):
        return reverse('home:profile-detail', kwargs={'pk': self.user.pk})

    def get_edit_url(self):
        return reverse('home:profile-edit', kwargs={'user__pk': self.user.pk})

    @classmethod
    def get_list_url(cls):
        return reverse('home:profile-list')

    # Other

    @property
    def institution_name(self):
        return self.institution.name if self.institution else ''

    @property
    def submitter(self):
        return self.user

    @property
    def is_reviewer(self):
        return self.user.groups.filter(
            name=ComsesGroups.REVIEWER.value).exists()

    @property
    def name(self):
        return self.user.get_full_name() or self.user.username

    @property
    def full_member(self):
        return self.user.groups.filter(
            name=ComsesGroups.FULL_MEMBER.value).exists()

    @full_member.setter
    def full_member(self, value):
        group = Group.objects.get(name=ComsesGroups.FULL_MEMBER.value)
        if value:
            self.user.groups.add(group)
        else:
            self.user.groups.remove(group)

    def is_messageable(self, user):
        return user.is_authenticated and user != self.user

    def __str__(self):
        return str(self.user)
示例#6
0
class News(TendenciBaseModel):
    CONTRIBUTOR_AUTHOR = 1
    CONTRIBUTOR_PUBLISHER = 2
    CONTRIBUTOR_CHOICES = ((CONTRIBUTOR_AUTHOR, _('Author')),
                           (CONTRIBUTOR_PUBLISHER, _('Publisher')))

    guid = models.CharField(max_length=40)
    slug = SlugField(_('URL Path'), unique=True)
    timezone = TimeZoneField(verbose_name=_('Time Zone'), default='US/Central', choices=get_timezone_choices(), max_length=100)
    headline = models.CharField(max_length=200, blank=True)
    summary = models.TextField(blank=True)
    body = tinymce_models.HTMLField()
    source = models.CharField(max_length=300, blank=True)
    first_name = models.CharField(_('First Name'), max_length=100, blank=True)
    last_name = models.CharField(_('Last Name'), max_length=100, blank=True)
    contributor_type = models.IntegerField(choices=CONTRIBUTOR_CHOICES,
                                           default=CONTRIBUTOR_AUTHOR)
    phone = models.CharField(max_length=50, blank=True)
    fax = models.CharField(max_length=50, blank=True)
    email = models.CharField(max_length=120, blank=True)
    website = models.CharField(max_length=300, blank=True)
    thumbnail = models.ForeignKey(NewsImage, default=None, null=True,
                                  on_delete=models.SET_NULL,
                                  help_text=_('The thumbnail image can be used on your homepage or sidebar if it is setup in your theme. The thumbnail image will not display on the news page.'))
    release_dt = models.DateTimeField(_('Release Date/Time'), null=True, blank=True)
    # used for better performance when retrieving a list of released news
    release_dt_local = models.DateTimeField(null=True, blank=True)
    syndicate = models.BooleanField(_('Include in RSS feed'), default=True)
    design_notes = models.TextField(_('Design Notes'), blank=True)
    groups = models.ManyToManyField(Group, default=get_default_group, related_name='group_news')
    tags = TagField(blank=True)

    #for podcast feeds
    enclosure_url = models.CharField(_('Enclosure URL'), max_length=500, blank=True) # for podcast feeds
    enclosure_type = models.CharField(_('Enclosure Type'),max_length=120, blank=True) # for podcast feeds
    enclosure_length = models.IntegerField(_('Enclosure Length'), default=0) # for podcast feeds

    use_auto_timestamp = models.BooleanField(_('Auto Timestamp'), default=False)

    # html-meta tags
    meta = models.OneToOneField(MetaTags, null=True, on_delete=models.SET_NULL)

    categories = GenericRelation(CategoryItem,
                                  object_id_field="object_id",
                                  content_type_field="content_type")

    perms = GenericRelation(ObjectPermission,
                              object_id_field="object_id",
                              content_type_field="content_type")

    objects = NewsManager()

    class Meta:
#         permissions = (("view_news",_("Can view news")),)
        verbose_name_plural = _("News")
        app_label = 'news'

    def get_meta(self, name):
        """
        This method is standard across all models that are
        related to the Meta model.  Used to generate dynamic
        meta information niche to this model.
        """
        return NewsMeta().get_meta(self, name)

    def get_absolute_url(self):
        return reverse('news.detail', args=[self.slug])

    def __str__(self):
        return self.headline

    def save(self, *args, **kwargs):
        if not self.id:
            self.guid = str(uuid.uuid4())
        self.assign_release_dt_local()

        photo_upload = kwargs.pop('photo', None)
        super(News, self).save(*args, **kwargs)

        if photo_upload and self.pk:
            image = NewsImage(
                object_id=self.pk,
                creator=self.creator,
                creator_username=self.creator_username,
                owner=self.owner,
                owner_username=self.owner_username
                    )
            photo_upload.file.seek(0)
            image.file.save(photo_upload.name, photo_upload)  # save file row
            image.save()  # save image row

            if self.thumbnail:
                self.thumbnail.delete()  # delete image and file row
            self.thumbnail = image  # set image

            self.save()

        if self.thumbnail:
            if self.is_public():
                set_s3_file_permission(self.thumbnail.file, public=True)
            else:
                set_s3_file_permission(self.thumbnail.file, public=False)

    @property
    def category_set(self):
        items = {}
        for cat in self.categories.select_related('category', 'parent'):
            if cat.category:
                items["category"] = cat.category
            elif cat.parent:
                items["sub_category"] = cat.parent
        return items

    def is_public(self):
        return all([self.allow_anonymous_view,
                self.status,
                self.status_detail in ['active']])

    @property
    def is_released(self):
        return self.release_dt_local <= datetime.now()

    @property
    def has_google_author(self):
        return self.contributor_type == self.CONTRIBUTOR_AUTHOR

    @property
    def has_google_publisher(self):
        return self.contributor_type == self.CONTRIBUTOR_PUBLISHER

    def assign_release_dt_local(self):
        """
        convert release_dt to the corresponding local time

        example:

        if
            release_dt: 2014-05-09 03:30:00
            timezone: US/Pacific
            settings.TIME_ZONE: US/Central
        then
            the corresponding release_dt_local will be: 2014-05-09 05:30:00
        """
        now = datetime.now()
        now_with_tz = adjust_datetime_to_timezone(now, settings.TIME_ZONE)
        if self.timezone and self.release_dt and self.timezone.zone != settings.TIME_ZONE:
            time_diff = adjust_datetime_to_timezone(now, self.timezone) - now_with_tz
            self.release_dt_local = self.release_dt + time_diff
        else:
            self.release_dt_local = self.release_dt
示例#7
0
class Article(TendenciBaseModel):
    CONTRIBUTOR_AUTHOR = 1
    CONTRIBUTOR_PUBLISHER = 2
    CONTRIBUTOR_CHOICES = ((CONTRIBUTOR_AUTHOR, _('Author')),
                           (CONTRIBUTOR_PUBLISHER, _('Publisher')))

    guid = models.CharField(max_length=40)
    slug = SlugField(_('URL Path'), unique=True)
    timezone = TimeZoneField(verbose_name=_('Time Zone'),
                             default='US/Central',
                             choices=get_timezone_choices(),
                             max_length=100)
    headline = models.CharField(max_length=200, blank=True)
    summary = models.TextField(blank=True)
    body = tinymce_models.HTMLField()
    source = models.CharField(max_length=300, blank=True)
    first_name = models.CharField(_('First Name'), max_length=100, blank=True)
    last_name = models.CharField(_('Last Name'), max_length=100, blank=True)
    contributor_type = models.IntegerField(choices=CONTRIBUTOR_CHOICES,
                                           default=CONTRIBUTOR_AUTHOR)
    phone = models.CharField(max_length=50, blank=True)
    fax = models.CharField(max_length=50, blank=True)
    email = models.CharField(max_length=120, blank=True)
    website = models.CharField(max_length=300, blank=True)
    thumbnail = models.ForeignKey(
        File,
        null=True,
        on_delete=models.SET_NULL,
        help_text=_(
            'The thumbnail image can be used on your homepage ' +
            'or sidebar if it is setup in your theme. The thumbnail image ' +
            'will not display on the news page.'))
    release_dt = models.DateTimeField(_('Release Date/Time'),
                                      null=True,
                                      blank=True)
    # used for better performance when retrieving a list of released articles
    release_dt_local = models.DateTimeField(null=True, blank=True)
    syndicate = models.BooleanField(_('Include in RSS feed'), default=True)
    featured = models.BooleanField(default=False)
    design_notes = models.TextField(_('Design Notes'), blank=True)
    group = models.ForeignKey(Group,
                              null=True,
                              default=get_default_group,
                              on_delete=models.SET_NULL)
    tags = TagField(blank=True)

    # for podcast feeds
    enclosure_url = models.CharField(_('Enclosure URL'),
                                     max_length=500,
                                     blank=True)
    enclosure_type = models.CharField(_('Enclosure Type'),
                                      max_length=120,
                                      blank=True)
    enclosure_length = models.IntegerField(_('Enclosure Length'), default=0)

    not_official_content = models.BooleanField(_('Official Content'),
                                               blank=True,
                                               default=True)

    # html-meta tags
    meta = models.OneToOneField(MetaTags, null=True, on_delete=models.SET_NULL)

    categories = GenericRelation(CategoryItem,
                                 object_id_field="object_id",
                                 content_type_field="content_type")
    perms = GenericRelation(ObjectPermission,
                            object_id_field="object_id",
                            content_type_field="content_type")

    objects = ArticleManager()

    class Meta:
        permissions = (("view_article", _("Can view article")), )
        verbose_name = _("Article")
        verbose_name_plural = _("Articles")
        app_label = 'articles'

    def get_meta(self, name):
        """
        This method is standard across all models that are
        related to the Meta model.  Used to generate dynamic
        methods coupled to this instance.
        """
        return ArticleMeta().get_meta(self, name)

    def get_absolute_url(self):
        return reverse('article', args=[self.slug])

    def get_version_url(self, hash):
        return reverse('article.version', args=[hash])

    def __str__(self):
        return self.headline

    def get_thumbnail_url(self):
        if not self.thumbnail:
            return u''

        return reverse('file', args=[self.thumbnail.pk])

    def save(self, *args, **kwargs):
        if not self.id:
            self.guid = str(uuid.uuid4())
        self.assign_release_dt_local()
        super(Article, self).save(*args, **kwargs)

    def assign_release_dt_local(self):
        """
        convert release_dt to the corresponding local time

        example:

        if
            release_dt: 2014-05-09 03:30:00
            timezone: US/Pacific
            settings.TIME_ZONE: US/Central
        then
            the corresponding release_dt_local will be: 2014-05-09 05:30:00
        """
        now = datetime.now()
        now_with_tz = adjust_datetime_to_timezone(now, settings.TIME_ZONE)
        if self.timezone and self.release_dt and self.timezone.zone != settings.TIME_ZONE:
            time_diff = adjust_datetime_to_timezone(
                now, self.timezone) - now_with_tz
            self.release_dt_local = self.release_dt + time_diff
        else:
            self.release_dt_local = self.release_dt

    def age(self):
        return datetime.now() - self.create_dt

    @property
    def category_set(self):
        items = {}
        for cat in self.categories.select_related('category', 'parent'):
            if cat.category:
                items["category"] = cat.category
            elif cat.parent:
                items["sub_category"] = cat.parent
        return items

    @property
    def has_google_author(self):
        return self.contributor_type == self.CONTRIBUTOR_AUTHOR

    @property
    def has_google_publisher(self):
        return self.contributor_type == self.CONTRIBUTOR_PUBLISHER
示例#8
0
class AdminModel(models.Model):
    '''
    An abstract model that adds some admin fields and overrides the save method to ensure that on every 
    save these fields are updated (who and when) 
    '''
    # Simple history and administrative fields
    created_by = models.ForeignKey(User,
                                   verbose_name='Created By',
                                   related_name='%(class)ss_created',
                                   editable=False,
                                   null=True,
                                   on_delete=models.SET_NULL)
    created_on = models.DateTimeField('Time of Creation',
                                      editable=False,
                                      null=True)
    created_on_tz = TimeZoneField('Time of Creation, Timezone',
                                  default=settings.TIME_ZONE,
                                  editable=False)

    last_edited_by = models.ForeignKey(User,
                                       verbose_name='Last Edited By',
                                       related_name='%(class)ss_last_edited',
                                       editable=False,
                                       null=True,
                                       on_delete=models.SET_NULL)
    last_edited_on = models.DateTimeField('Time of Last Edit',
                                          editable=False,
                                          null=True)
    last_edited_on_tz = TimeZoneField('Time of Last Edit, Timezone',
                                      default=settings.TIME_ZONE,
                                      editable=False)

    def update_admin_fields(self):
        '''
        Update the CoGs admin fields on an object (whenever it is saved).
        '''
        now = timezone.now()
        usr = CuserMiddleware.get_user()

        if hasattr(self, "last_edited_by"):
            self.last_edited_by = usr

        if hasattr(self, "last_edited_on"):
            self.last_edited_on = now

        if hasattr(self, "last_edited_on_tz"):
            self.last_edited_on_tz = str(get_current_timezone())

        # We infer that if the object has pk it was being edited and if it has none it was being created
        if self.pk is None:
            if hasattr(self, "created_by"):
                self.created_by = usr

            if hasattr(self, "created_on"):
                self.created_on = now

            if hasattr(self, "created_on_tz"):
                self.created_on_tz = str(get_current_timezone())

    def save(self, *args, **kwargs):
        self.update_admin_fields()
        super().save(*args, **kwargs)

    class Meta:
        get_latest_by = "created_on"
        abstract = True
示例#9
0
class Airport(models.Model):

    title = models.CharField(
        verbose_name='Long Name of Airport', max_length=50)
    timezone = TimeZoneField(default='US/Eastern')
    abrev = models.CharField(
        verbose_name='Airport Abreviation Code',
        max_length=4,
        primary_key=True)
    latitude = models.FloatField(
        validators=[MinValueValidator(-90), MaxValueValidator(90)])
    longitude = models.FloatField(
        validators=[MinValueValidator(-180), MaxValueValidator(180)])
    sw_airport = models.BooleanField(verbose_name='Southwest Airport')

    country = models.CharField(max_length=20, blank=True)
    state = models.CharField(max_length=20, blank=True)

    objects = AirportManager()

    sw_airport.admin_order_field = 'title'

    def __str__(self):
        # Return the title and abrev as the default string
        return self.title + " - " + self.abrev

    def _get_sub_loc(self, key):
        # Here we use geolocator to get the proper key
        geolocator = Nominatim()
        location = geolocator.reverse("{:f}, {:f}".format(
            self.latitude, self.longitude), timeout=10)

        # Lots and lots of error checking...looking for error from Geolocator
        # and for missing fields for international or other addresses

        if 'error' in location.raw:
            # Got an error back from geolocator
            raise ValidationError(_(
                "Geolocator error: %(error)s - Check you have the right Lat/Long or that you have connection"),
                params=location.raw, code='geolocator')

        # Got a response...but we may be missing keys...looking here
        try:
            return location.raw['address'][key]
        except KeyError as err:
            if err == 'address':
                raise ValidationError(
                    _('Got a response from Geolocator, but had no address'), code='no_address')
            elif err == key:
                raise ValidationError(_('Got a response from Geolocator, had an address, but didnt have key: %(key)s'), params={
                                      'key': err}, code='no_{}'.format(err))
            else:
                raise ValidationError(_('Got a response from Geolocator, had an address,KEY_ERROR of some kind %(raw)s'), params={
                                      'raw': location.raw}, code='some_key')
        except:
            raise ValidationError(
                _('Geolocator - NO CLUE WHAT WENT WRONG'), code='no_clue')

    def get_tz_obj(self):
        if isinstance(self.timezone, string_types):
            return pytz.timezone(self.timezone)
        else:
            return self.timezone

    def get_country_code(self):
        return self._get_sub_loc('country_code')

    def get_state(self):
        return self._get_sub_loc('state')

    def add_loc_fields(self):
        if (self.country is None) or (self.country == ''):
            self.country = self.get_country_code()
        if ((self.state is None) or (self.state == '')) and self.country == 'us':
            self.state = self.get_state()
示例#10
0
class RRule(models.Model):
    """
    Model that will hold rrule details and generate recurrences to be handled by the supplied handler
    """

    # Params used to generate the rrule
    rrule_params = JSONField()

    # Any meta data associated with the object that created this rule
    meta_data = JSONField(default=dict)

    # The timezone all dates should be converted to
    time_zone = TimeZoneField(default='UTC')

    # The last occurrence date that was handled
    last_occurrence = models.DateTimeField(null=True, default=None)

    # The next occurrence date that should be handled
    next_occurrence = models.DateTimeField(null=True, default=None)

    # A python path to the handler class used to handle when a recurrence occurs for this rrule
    # The configuration class must extend ambition_utils.rrule.handler.OccurrenceHandler
    occurrence_handler_path = models.CharField(max_length=500, blank=False, null=False)

    # Custom object manager
    objects = RRuleManager()

    def get_occurrence_handler_class_instance(self):
        """
        Gets an instance of the occurrence handler class associated with this rrule
        :rtype: ambition_utils.rrule.handler.OccurrenceHandler
        :return: The instance
        """
        return import_string(self.occurrence_handler_path)()

    def get_rrule(self):
        """
        Builds the rrule object by restoring all the params.
        The dtstart param will be converted to local time if it is set.
        :rtype: rrule
        """
        params = copy.deepcopy(self.rrule_params)

        # Convert next scheduled from utc back to time zone
        if params.get('dtstart') and not hasattr(params.get('dtstart'), 'date'):
            params['dtstart'] = parser.parse(params['dtstart'])

        # Convert until date from utc back to time zone
        if params.get('until') and not hasattr(params.get('until'), 'date'):
            params['until'] = parser.parse(params['until'])

        # Always cache
        params['cache'] = True

        # Return the rrule
        return rrule(**params)

    def get_next_occurrence(self, last_occurrence=None, force=False):
        """
        Builds the rrule and returns the next date in the series or None of it is the end of the series
        :param last_occurrence: The last occurrence that was generated
        :param force: If the next occurrence is none, force the rrule to generate another
        :rtype: rrule or None
        """
        # Get the last occurrence
        last_occurrence = last_occurrence or self.last_occurrence or datetime.utcnow()

        # Get the rule
        rule = self.get_rrule()

        # Convert to local time zone for getting next occurrence, otherwise time zones ahead of utc will return the same
        last_occurrence = fleming.convert_to_tz(last_occurrence, self.time_zone, return_naive=True)

        # Generate the next occurrence
        next_occurrence = rule.after(last_occurrence)

        # If next occurrence is none and force is true, force the rrule to generate another date
        if next_occurrence is None and force:
            # Keep a reference to the original rrule_params
            original_rrule_params = {}
            original_rrule_params.update(self.rrule_params)

            # Remove any limiting params
            self.rrule_params.pop('count', None)
            self.rrule_params.pop('until', None)

            # Refetch the rule
            rule = self.get_rrule()

            # Generate the next occurrence
            next_occurrence = rule.after(last_occurrence)

            # Restore the rrule params
            self.rrule_params = original_rrule_params

        # If there is a next occurrence, convert to utc
        if next_occurrence:
            next_occurrence = self.convert_to_utc(next_occurrence)

        # Return the next occurrence
        return next_occurrence

    def update_next_occurrence(self, save=True):
        """
        Sets the next_occurrence property to the next time in the series and sets the last_occurrence property
        to the previous value of next_occurrence. If the save option is True, the model will be saved. The
        save flag is typically set to False when wanting to bulk update records after updating the values
        of many models.
        :param save: Flag to save the model after updating the schedule.
        :type save: bool
        """
        if not self.next_occurrence:
            return None

        # Only handle if the current date is >= next occurrence
        if datetime.utcnow() < self.next_occurrence:
            return False

        self.last_occurrence = self.next_occurrence
        self.next_occurrence = self.get_next_occurrence(self.last_occurrence)

        # Only save if the flag is true
        if save:
            self.save(update_fields=['last_occurrence', 'next_occurrence'])

    def convert_to_utc(self, dt):
        """
        Treats the datetime object as being in the timezone of self.timezone and then converts it to utc timezone.
        :type dt: datetime
        """
        # Add timezone info
        dt = fleming.attach_tz_if_none(dt, self.time_zone)

        # Convert to utc
        dt = fleming.convert_to_tz(dt, pytz.utc, return_naive=True)

        return dt

    def refresh_next_occurrence(self, current_time=None):
        """
        Sets the next occurrence date based on the current rrule param definition. The date will be after the
        specified current_time or utcnow.
        :param current_time: Optional datetime object to compute the next time from
        """
        # Get the current time or go off the specified current time
        current_time = current_time or datetime.utcnow()

        # Next occurrence is in utc here
        next_occurrence = self.get_next_occurrence(last_occurrence=current_time)

        # Check if the start time is different but still greater than now
        if next_occurrence != self.next_occurrence and next_occurrence > datetime.utcnow():
            self.next_occurrence = next_occurrence

    def save(self, *args, **kwargs):
        """
        Saves the rrule model to the database. If this is a new object, the first next_scheduled time is
        determined and set. The `dtstart` and `until` objects will be safely encoded as strings if they are
        datetime objects.
        """

        # Check if this is a new rrule object
        if self.pk is None:
            # Convert next scheduled from utc back to time zone
            if self.rrule_params.get('dtstart') and not hasattr(self.rrule_params.get('dtstart'), 'date'):
                self.rrule_params['dtstart'] = parser.parse(self.rrule_params['dtstart'])

            # Convert until date from utc back to time zone
            if self.rrule_params.get('until') and not hasattr(self.rrule_params.get('until'), 'date'):
                self.rrule_params['until'] = parser.parse(self.rrule_params['until'])

            # Get the first scheduled time according to the rrule (this converts from utc back to local time)
            self.next_occurrence = self.get_rrule()[0]

            # Convert back to utc before saving
            self.next_occurrence = self.convert_to_utc(self.next_occurrence)

        # Serialize the datetime objects if they exist
        if self.rrule_params.get('dtstart') and hasattr(self.rrule_params.get('dtstart'), 'date'):
            self.rrule_params['dtstart'] = self.rrule_params['dtstart'].strftime('%Y-%m-%d %H:%M:%S')

        if self.rrule_params.get('until') and hasattr(self.rrule_params.get('until'), 'date'):
            self.rrule_params['until'] = self.rrule_params['until'].strftime('%Y-%m-%d %H:%M:%S')

        # Call the parent save method
        super().save(*args, **kwargs)
 def test_invalid_choices_display(self):
     self.assertRaises(ValueError,
                       lambda: TimeZoneField(choices_display='invalid'))
 def test_deconstruct(self):
     for org_field in self.test_fields:
         name, path, args, kwargs = org_field.deconstruct()
         new_field = TimeZoneField(*args, **kwargs)
         self.assertEqual(org_field.max_length, new_field.max_length)
         self.assertEqual(org_field.choices, new_field.choices)
class TimeZoneFieldDeconstructTestCase(TestCase):

    test_fields = (
        TimeZoneField(),
        TimeZoneField(default='UTC'),
        TimeZoneField(max_length=42),
        TimeZoneField(choices=[
            (pytz.timezone('US/Pacific'), 'US/Pacific'),
            (pytz.timezone('US/Eastern'), 'US/Eastern'),
        ]),
        TimeZoneField(choices=[
            (pytz.timezone(b'US/Pacific'), b'US/Pacific'),
            (pytz.timezone(b'US/Eastern'), b'US/Eastern'),
        ]),
        TimeZoneField(choices=[
            ('US/Pacific', 'US/Pacific'),
            ('US/Eastern', 'US/Eastern'),
        ]),
        TimeZoneField(choices=[
            (b'US/Pacific', b'US/Pacific'),
            (b'US/Eastern', b'US/Eastern'),
        ]),
    )

    def test_deconstruct(self):
        for org_field in self.test_fields:
            name, path, args, kwargs = org_field.deconstruct()
            new_field = TimeZoneField(*args, **kwargs)
            self.assertEqual(org_field.max_length, new_field.max_length)
            self.assertEqual(org_field.choices, new_field.choices)

    def test_full_serialization(self):
        # ensure the values passed to kwarg arguments can be serialized
        # the recommended 'deconstruct' testing by django docs doesn't cut it
        # https://docs.djangoproject.com/en/1.7/howto/custom-model-fields/#field-deconstruction
        # replicates https://github.com/mfogel/django-timezone-field/issues/12
        for field in self.test_fields:
            # ensuring the following call doesn't throw an error
            MigrationWriter.serialize(field)

    def test_from_db_value(self):
        """
        Verify that the field can handle data coming back as bytes from the
        db.
        """
        field = TimeZoneField()

        # django 1.11 signuature
        value = field.from_db_value(b'UTC', None, None, None)
        self.assertEqual(pytz.UTC, value)

        # django 2.0+ signuature
        value = field.from_db_value(b'UTC', None, None)
        self.assertEqual(pytz.UTC, value)

    def test_default_kwargs_not_frozen(self):
        """
        Ensure the deconstructed representation of the field does not contain
        kwargs if they match the default.
        Don't want to bloat everyone's migration files.
        """
        field = TimeZoneField()
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('choices', kwargs)
        self.assertNotIn('max_length', kwargs)

    def test_specifying_defaults_not_frozen(self):
        """
        If someone's matched the default values with their kwarg args, we
        shouldn't bothering freezing those.
        """
        field = TimeZoneField(max_length=63)
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('max_length', kwargs)

        choices = [(pytz.timezone(tz), tz.replace('_', ' '))
                   for tz in pytz.common_timezones]
        field = TimeZoneField(choices=choices)
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('choices', kwargs)

        choices = [(tz, tz.replace('_', ' ')) for tz in pytz.common_timezones]
        field = TimeZoneField(choices=choices)
        name, path, args, kwargs = field.deconstruct()
        self.assertNotIn('choices', kwargs)
 def createField():
     TimeZoneField('a verbose name', 'a name', True, 42)
 def test_some_positional_args_ok(self):
     TimeZoneField('a verbose name', 'a name', True)
示例#16
0
class TimerUser(AbstractUser):
    timezone = TimeZoneField(default='Canada/Mountain')