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', )
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 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)
class TimerUser(AbstractUser): timezone = TimeZoneField(default='Canada/Mountain')
def createField(): TimeZoneField('a verbose name', 'a name', True, 42)
class User(AbstractUser): """ Project's base user model """ LANGUAGE_CHOICES = ( ('en-us', 'English'), ('pt-pt', _('Portuguese')), ) organization = models.CharField(null=True, blank=True, max_length=80, verbose_name=_('Organization')) public_key = models.TextField(blank=True, null=True, verbose_name=_('Public key')) fingerprint = models.CharField(null=True, blank=True, max_length=50, verbose_name=_('Fingerprint')) keyserver_url = models.URLField(null=True, blank=True, verbose_name=_('Key server URL')) timezone = TimeZoneField(default='UTC', verbose_name=_('Timezone')) language = models.CharField(default="en-us", max_length=16, choices=LANGUAGE_CHOICES, verbose_name=_('Language')) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.base_fingerprint = self.fingerprint def save(self, *args, **kwargs): ip = kwargs.pop('ip', None) agent = kwargs.pop('agent', '') with transaction.atomic(): super().save(*args, **kwargs) if self.base_fingerprint != self.fingerprint: self.keychanges.create(user=self, prev_fingerprint=self.base_fingerprint, to_fingerprint=self.fingerprint, ip_address=ip, agent=agent) self.base_fingerprint = self.fingerprint def has_setup_complete(self): if self.public_key and self.fingerprint: return True return False @property def has_github_login(self): return self.socialaccount_set.filter(provider='github').count() >= 1 @property def has_public_key(self): return True if self.public_key else False @property def has_keyserver_url(self): return True if self.keyserver_url else False
class Resource(SoftDeletableModel, TimeStampedModel): name = models.CharField(verbose_name=_("Name"), max_length=255, null=True, blank=True) description = models.TextField(verbose_name=_("Description"), null=True, blank=True) address = models.TextField(verbose_name=_("Street address"), null=True, blank=True) resource_type = EnumField( ResourceType, verbose_name=_("Resource type"), max_length=100, default=ResourceType.UNIT, ) children = models.ManyToManyField( "self", verbose_name=_("Sub resources"), related_name="parents", blank=True, symmetrical=False, ) organization = models.ForeignKey( Organization, on_delete=models.PROTECT, related_name="resources", db_index=True, null=True, blank=True, ) data_sources = models.ManyToManyField(DataSource, through="ResourceOrigin") last_modified_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, editable=False, ) extra_data = models.JSONField(verbose_name=_("Extra data"), null=True, blank=True) is_public = models.BooleanField(default=True) timezone = TimeZoneField(default=get_resource_default_timezone, null=True, blank=True) # Denormalized values from the parent resources ancestry_is_public = models.BooleanField(null=True, blank=True) ancestry_data_source = ArrayField( models.CharField(max_length=255), null=True, blank=True, ) ancestry_organization = ArrayField( models.CharField(max_length=255), null=True, blank=True, ) class Meta: verbose_name = _("Resource") verbose_name_plural = _("Resources") def __str__(self): return str(self.name) @property def _history_user(self): return self.last_modified_by @_history_user.setter def _history_user(self, value): self.last_modified_by = value def get_daily_opening_hours(self, start_date, end_date): # TODO: This is just an MVP. Things yet to do: # - Support full_day all_daily_opening_hours = defaultdict(list) # Need to get one day before the start to handle cases where the previous days # opening hours extend to the next day. start_minus_one_day = start_date - relativedelta(days=1) # We can't filter the date_periods queryset here because we # want to allow the callers to use prefetch_related. for period in self.date_periods.all(): # Filter the dates in code instead if period.start_date is not None and period.start_date > end_date: continue if period.end_date is not None and period.end_date < start_date: continue period_daily_opening_hours = period.get_daily_opening_hours( start_minus_one_day, end_date) for the_date, time_items in period_daily_opening_hours.items(): all_daily_opening_hours[the_date].extend(time_items) days = list(all_daily_opening_hours.keys()) days.sort() processed_opening_hours = {} for day in days: previous_day = day - relativedelta(days=1) # Add the time spans that might extend from the previous day to the # daily opening hours list for them to be considered in the combining step. for el in processed_opening_hours.get(previous_day, []): if not el.end_time_on_next_day: continue all_daily_opening_hours[day].append(el.get_next_day_part()) processed_opening_hours[day] = combine_and_apply_override( all_daily_opening_hours[day]) # Remove the excessive day from the start if start_minus_one_day in processed_opening_hours: del processed_opening_hours[start_minus_one_day] return processed_opening_hours def _get_parent_data(self, acc=None): if acc is None: acc = { "is_public": None, "data_sources": set(), "organizations": set(), } parents = ( self.parents.all().select_related("organization").prefetch_related( "origins", "origins__data_source", )) if not parents: return acc for parent in parents: if acc["is_public"] is None: acc["is_public"] = parent.is_public if not parent.is_public: acc["is_public"] = False acc["data_sources"].update( [i.data_source.id for i in parent.origins.all()]) if parent.organization: acc["organizations"].add(parent.organization.id) parent._get_parent_data(acc) return acc def update_ancestry(self, update_child_ancestry_fields=True): data = self._get_parent_data() self.ancestry_is_public = data["is_public"] self.ancestry_data_source = list(data["data_sources"]) self.ancestry_organization = list(data["organizations"]) self.save(update_child_ancestry_fields=update_child_ancestry_fields) def save( self, force_insert=False, force_update=False, using=None, update_fields=None, update_child_ancestry_fields=True, ): super().save( force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields, ) if update_child_ancestry_fields: for child in self.children.all(): child.update_ancestry() def get_ancestors(self, acc=None): if acc is None: acc = set() parents = self.parents.all() if not parents: return acc for parent in parents: if parent == self or parent in acc: continue acc.add(parent) parent.get_ancestors(acc) return acc
class Group(BaseModel, LocationModel, ConversationMixin): objects = GroupManager() name = models.CharField(max_length=settings.NAME_MAX_LENGTH, unique=True) description = models.TextField(blank=True) members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='groups', through='GroupMembership') password = models.CharField(max_length=255, blank=True) public_description = models.TextField(blank=True) timezone = TimeZoneField(default='Europe/Berlin', null=True, blank=True) slack_webhook = models.CharField(max_length=255, blank=True) active_agreement = models.OneToOneField('groups.Agreement', related_name='active_group', null=True, on_delete=models.SET_NULL) def __str__(self): return '{}'.format(self.name) def send_notifications(self): if self.slack_webhook.startswith('https://hooks.slack.com/services/'): for s in self.store.all(): # get all pick-ups within the notification range for p in s.pickup_dates.filter( date__lt=timezone.now() + relativedelta(hours=s.upcoming_notification_hours), date__gt=timezone.now()): p.notify_upcoming_via_slack() def add_member(self, user, history_payload=None): GroupMembership.objects.create(group=self, user=user) History.objects.create(typus=HistoryTypus.GROUP_JOIN, group=self, users=[ user, ], payload=history_payload) def remove_member(self, user): History.objects.create(typus=HistoryTypus.GROUP_LEAVE, group=self, users=[ user, ]) GroupMembership.objects.filter(group=self, user=user).delete() def is_member(self, user): return not user.is_anonymous and GroupMembership.objects.filter( group=self, user=user).exists() def is_member_with_role(self, user, role_name): return not user.is_anonymous and GroupMembership.objects.filter( group=self, user=user, roles__contains=[role_name]).exists() def accept_invite(self, user, invited_by, invited_at): self.add_member(user, history_payload={ 'invited_by': invited_by.id, 'invited_at': invited_at.isoformat(), 'invited_via': 'e-mail' })
class Project(models.Model): DEFAULT_TIMEZONE = settings.TIME_ZONE program = models.ForeignKey( Program, blank=False, null=False, on_delete=models.CASCADE, help_text="The program this project belongs to.") name = models.CharField( max_length=300, null=False, blank=False, unique=True, verbose_name="Name", help_text="Enter a name for the project (required).") code = models.CharField( max_length=30, null=True, blank=True, verbose_name="Code", help_text="Provide a brief code or acronym for this project. " "This code could be used for prefixing site codes.") datum = models.IntegerField( null=True, blank=True, choices=DATUM_CHOICES, default=MODEL_SRID, verbose_name="Default Datum", help_text= "The datum all locations will be assumed to have unless otherwise specified." ) timezone = TimeZoneField( blank=True, default=DEFAULT_TIMEZONE, help_text="The Timezone of your project e.g 'Australia/Perth.") attributes = JSONField( null=True, blank=True, help_text= "Define here all specific attributes of your project in the form of json " "'attribute name': 'attribute value") description = models.TextField(null=True, blank=True, verbose_name="Description", help_text="") geometry = models.GeometryField( srid=MODEL_SRID, spatial_index=True, null=True, blank=True, editable=True, verbose_name="Extent", help_text="The boundary of your project (not required). " "Can also be calculated from the extents of the project sites") site_data_package = JSONField( null=True, blank=True, verbose_name='Site attributes schema', help_text='Define here the attributes that all your sites will share. ' 'This allows validation when importing sites.') custodians = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=False, help_text= "Users that have write/upload access to the data of this project.") def is_custodian(self, user): return user in self.custodians.all() def is_data_engineer(self, user): return self.program.is_data_engineer(user) # API permissions @staticmethod def has_read_permission(request): return True def has_object_read_permission(self, request): return True @staticmethod def has_metadata_permission(request): return True def has_object_metadata_permission(self, request): return True @staticmethod def has_create_permission(request): """ Admin or data_engineer of the program the project would belong to. Check that the user is a data_engineer of the program pk passed in the POST data. :param request: :return: """ result = False if is_admin(request.user): result = True elif 'program' in request.data: program = Program.objects.filter( pk=request.data['program']).first() result = program is not None and program.is_data_engineer( request.user) return result @staticmethod def has_update_permission(request): """ The update is managed at the object level (see below) :param request: :return: """ return True def has_object_update_permission(self, request): """ Admin or program data_engineer or project custodian :param request: :return: """ user = request.user return is_admin(user) or self.program.is_data_engineer(user) @staticmethod def has_destroy_permission(request): return True def has_object_destroy_permission(self, request): """ Admin or program data_engineer or project custodian :param request: :return: """ user = request.user return is_admin(user) or self.program.is_data_engineer(user) @property def centroid(self): return self.geometry.centroid if self.geometry else None @property def extent(self): return self.geometry.extent if self.geometry else None @property def dataset_count(self): return self.projects.count() @property def site_count(self): return self.site_set.count() @property def record_count(self): return Record.objects.filter(dataset__project=self).count() class Meta: ordering = ['name'] def __str__(self): return self.name
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
class Site(ChangeLoggedModel, CustomFieldModel): """ A Site represents a geographic location within a network; typically a building or campus. The optional facility field can be used to include an external designation, such as a data center name (e.g. Equinix SV6). """ name = models.CharField(max_length=50, unique=True) _name = NaturalOrderingField(target_field='name', max_length=100, blank=True) slug = models.SlugField(unique=True) status = models.CharField(max_length=50, choices=SiteStatusChoices, default=SiteStatusChoices.STATUS_ACTIVE) region = models.ForeignKey(to='dcim.Region', on_delete=models.SET_NULL, related_name='sites', blank=True, null=True) tenant = models.ForeignKey(to='tenancy.Tenant', on_delete=models.PROTECT, related_name='sites', blank=True, null=True) facility = models.CharField(max_length=50, blank=True, help_text='Local facility ID or description') asn = ASNField(blank=True, null=True, verbose_name='ASN', help_text='32-bit autonomous system number') time_zone = TimeZoneField(blank=True) description = models.CharField(max_length=200, blank=True) physical_address = models.CharField(max_length=200, blank=True) shipping_address = models.CharField(max_length=200, blank=True) latitude = models.DecimalField(max_digits=8, decimal_places=6, blank=True, null=True, help_text='GPS coordinate (latitude)') longitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True, help_text='GPS coordinate (longitude)') contact_name = models.CharField(max_length=50, blank=True) contact_phone = models.CharField(max_length=20, blank=True) contact_email = models.EmailField(blank=True, verbose_name='Contact E-mail') comments = models.TextField(blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') images = GenericRelation(to='extras.ImageAttachment') tags = TaggableManager(through=TaggedItem) objects = RestrictedQuerySet.as_manager() csv_headers = [ 'name', 'slug', 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments', ] clone_fields = [ 'status', 'region', 'tenant', 'facility', 'asn', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', ] STATUS_CLASS_MAP = { SiteStatusChoices.STATUS_PLANNED: 'info', SiteStatusChoices.STATUS_STAGING: 'primary', SiteStatusChoices.STATUS_ACTIVE: 'success', SiteStatusChoices.STATUS_DECOMMISSIONING: 'warning', SiteStatusChoices.STATUS_RETIRED: 'danger', } class Meta: ordering = ('_name', ) def __str__(self): return self.name def get_absolute_url(self): return reverse('dcim:site', args=[self.slug]) def to_csv(self): return ( self.name, self.slug, self.get_status_display(), self.region.name if self.region else None, self.tenant.name if self.tenant else None, self.facility, self.asn, self.time_zone, self.description, self.physical_address, self.shipping_address, self.latitude, self.longitude, self.contact_name, self.contact_phone, self.contact_email, self.comments, ) def get_status_class(self): return self.STATUS_CLASS_MAP.get(self.status)
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 (LEGACY)', editable=False) datetime = models.DateTimeField() 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', on_delete=models.PROTECT) 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', on_delete=models.PROTECT) 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', on_delete=models.PROTECT) 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', on_delete=models.PROTECT) 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', on_delete=models.PROTECT) # Fields for Horaro schedule import horaro_id = models.CharField(max_length=100, verbose_name='Event ID', blank=True, default='', help_text='ID or slug for Horaro event') horaro_game_col = models.IntegerField( verbose_name='Game Column', blank=True, null=True, help_text='Column index for game info (start at 0)') horaro_category_col = models.IntegerField( verbose_name='Category Column', blank=True, null=True, help_text='Column index for category info (start at 0)') horaro_runners_col = models.IntegerField( verbose_name='Runners Column', blank=True, null=True, help_text='Column index for runner info (start at 0)') horaro_commentators_col = models.IntegerField( verbose_name='Commentators Column', blank=True, null=True, help_text='Column index for commentator info (start at 0)') # Fields for Tiltify donation import tiltify_enable_sync = models.BooleanField( default=False, verbose_name='Enable Tiltify Sync', help_text='Sync donations for this event via the Tiltify API') tiltify_api_key = models.CharField(max_length=100, verbose_name='Tiltify Campaign API Key', blank=True, default='') # Fields for Twitch chat announcements twitch_channel = models.CharField( max_length=100, verbose_name='Channel Name', blank=True, default='', help_text='Announcements will be made to this channel') twitch_login = models.CharField( max_length=100, verbose_name='Username', blank=True, default='', help_text='Username to use for chat announcements') twitch_oauth = models.CharField( max_length=200, verbose_name='OAuth Password', blank=True, default='', help_text='Get one here: http://www.twitchapps.com/tmi') def __str__(self): return self.name def natural_key(self): return (self.short, ) def save(self, *args, **kwargs): if self.datetime is not None: if self.datetime.tzinfo is None or self.datetime.tzinfo.utcoffset( self.datetime) is None: self.datetime = self.timezone.localize(self.datetime) super(Event, self).save(*args, **kwargs) def clean(self): errors = {} if self.id and self.id < 1: raise ValidationError('Event ID must be positive and non-zero') if not re.match('^\w+$', self.short): errors['short'] = '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: errors[ 'donationemailsender'] = 'Must specify a donation email sender if automailing is used' # If Tiltify sync is enabled, the API key must be populated. if self.tiltify_enable_sync and not self.tiltify_api_key: errors[ 'tiltify_api_key'] = 'Must be populated if Tiltify sync is enabled' # If any Twitch chat fields are filled in, they all must be. if self.twitch_channel or self.twitch_login or self.twitch_oauth: if not self.twitch_channel: errors[ 'twitch_channel'] = 'Must be filled if enabling Twitch chat announcements' if not self.twitch_login: errors[ 'twitch_login'] = '******' if not self.twitch_oauth: errors[ 'twitch_oauth'] = 'Must be filled if enabling Twitch chat announcements' # Don't put the "oauth:" starting part on the token. IRC code will add this automatically. if self.twitch_oauth.startswith("oauth:"): self.twitch_oauth = self.twitch_oauth[6:] if errors: raise ValidationError(errors) @property def date(self): return self.datetime.date() # Extra field for displaying Horaro columns on admin UI. def admin_horaro_check_cols(self): return format_html('<span id="horaro_cols"></span>') admin_horaro_check_cols.allow_tags = True admin_horaro_check_cols.short_description = "Schedule Columns" class Meta: app_label = 'tracker' get_latest_by = 'datetime' permissions = (('can_edit_locked_events', 'Can edit locked events'), ) ordering = ('datetime', 'name')
class UserProfile(caching.base.CachingMixin, models.Model): user = models.OneToOneField(User) bio = models.CharField(max_length=500, blank=True, null=True) time_zone = TimeZoneField(blank=True, null=True) latitude = models.FloatField(null=True, blank=True) longitude = models.FloatField(null=True, blank=True) location = models.CharField(max_length=255, null=True, blank=True) birthday = models.DateField(null=True, blank=True) tagline = models.CharField(max_length=180, null=True, blank=True) display_name = models.CharField(max_length=120, null=True, blank=True) has_commented = models.BooleanField(default=False) logo = models.ImageField(max_length=1024, upload_to=logo_file_path, blank=True, null=True) dashboard_enabled = models.BooleanField(default=False) is_online = models.BooleanField(default=False) last_online_on = models.DateTimeField(blank=True, null=True) channel = models.CharField(max_length=255, blank=True, null=True) class Meta: verbose_name = 'User Profile' verbose_name_plural = 'User Profiles' def get_uhash(self): return hashlib.md5(self.user.username).hexdigest() def get_color_code(self): uhash = self.get_uhash() r = '0x%s' % str(uhash[0:2]) g = '0x%s' % str(uhash[2:4]) b = '0x%s' % str(uhash[4:6]) rgb = (int(r, 16), int(g, 16), int(b, 16)) return rgb def generate_default_avatar(self): from cStringIO import StringIO from django.core.files.uploadedfile import SimpleUploadedFile from PIL import Image, ImageFont, ImageDraw from avatar.models import Avatar tmpname = '%s.png' % self.get_uhash() code = self.get_color_code() mode = "RGB" W, H = settings.SIMPLEAVATAR_SIZE font = ImageFont.truetype(settings.SIMPLEAVATAR_FONT, 256) text = self.user.username[:1].upper() im = Image.new(mode, (W, H), code) draw = ImageDraw.Draw(im) text_x, text_y = font.getsize(text) x = (W - text_x) / 2.0 y = ((H - text_y) / 2.0) - (text_y / 2.0) draw.text((x, y), text, font=font, fill=(255, 255, 255, 100)) # Write new avatar to memory. tmphandle = StringIO() im.save(tmphandle, 'png') tmphandle.seek(0) suf = SimpleUploadedFile(tmpname, tmphandle.read(), content_type='image/png') av = Avatar(user=self.user, primary=True) av.avatar.save(tmpname, suf, save=False) av.save() def get_absolute_url(self): return '' def __unicode__(self): return "%s's Profile" % self.user
class Member(models.Model): id = models.CharField(max_length=60, primary_key=True) real_name = models.CharField(max_length=60) tz = TimeZoneField(default='Europe/London')
class User(TrackerModelMixin, AbstractUser): class ActivityStreamFilters(models.TextChoices): USERS = "users", _("Limited to only content from people I'm following") TAGS = "tags", _("Limited to only tags I'm following") name = models.CharField(_("Full name"), blank=True, max_length=255) bio = MarkdownField(blank=True) avatar = ImageField(upload_to="avatars", null=True, blank=True) language = models.CharField( max_length=6, choices=settings.LANGUAGES, default=settings.LANGUAGE_CODE, ) default_timezone = TimeZoneField(default=settings.TIME_ZONE) activity_stream_filters = ChoiceArrayField( models.CharField(max_length=12, choices=ActivityStreamFilters.choices), default=list, blank=True, ) show_external_images = models.BooleanField(default=True) show_sensitive_content = models.BooleanField(default=False) show_embedded_content = models.BooleanField(default=False) send_email_notifications = models.BooleanField(default=True) dismissed_notices = ArrayField(models.CharField(max_length=30), default=list) following = models.ManyToManyField("self", related_name="followers", blank=True, symmetrical=False) blocked = models.ManyToManyField("self", related_name="blockers", blank=True, symmetrical=False) following_tags = models.ManyToManyField(Tag, related_name="+", blank=True) blocked_tags = models.ManyToManyField(Tag, related_name="+", blank=True) search_document = SearchVectorField(null=True, editable=False) search_indexer = SearchIndexer(("A", "username"), ("B", "name"), ("C", "bio")) tracked_fields = ["avatar", "name", "bio"] objects = UserManager() class Meta(AbstractUser.Meta): indexes = [ GinIndex(fields=["search_document"]), models.Index(fields=["name", "username"]), ] def get_absolute_url(self): return reverse("users:activities", args=[self.username]) def get_display_name(self): """Displays full name or username Returns: str: full display name """ return self.name or self.username def get_initials(self): return "".join([n[0].upper() for n in self.get_display_name().split()][:2]) def get_notifications(self): """Returns notifications where the user is the target content object, *not* necessarily the actor or recipient. Returns: QuerySet """ return get_generic_related_queryset(self, Notification) @cached_property def member_cache(self): """ Returns: A MemberCache instance of membership status/roles across all communities the user belongs to. """ mc = MemberCache() for community_id, role, active in Membership.objects.filter( member=self).values_list("community", "role", "active"): mc.add_role(community_id, role, active) return mc def has_role(self, community, *roles): """Checks if user has given role in the community, if any. Result is cached. Args: community (Community) *roles: roles i.e. one or more of "member", "moderator", "admin". If empty assumes any role. Returns: bool: if user has any of these roles """ return self.member_cache.has_role(community.id, roles) def is_admin(self, community): return self.has_role(community, Membership.Role.ADMIN) def is_moderator(self, community): return self.has_role(community, Membership.Role.MODERATOR) def is_member(self, community): return self.has_role(community, Membership.Role.MEMBER) def is_active_member(self, community): """Checks if user an active member of any role. Returns: bool """ return self.has_role(community) def is_inactive_member(self, community): """Checks if user has an inactive membership for this community. Returns: bool """ return self.member_cache.is_inactive(community.id) def is_blocked(self, user): """Check if user is blocking this other user, or is blocked by this other user. Args: user (User) Returns: bool """ if self == user: return False return self.get_blocked_users().filter(pk=user.id).exists() def is_activity_stream_tags_filter(self): return self.ActivityStreamFilters.TAGS in self.activity_stream_filters def is_activity_stream_users_filter(self): return self.ActivityStreamFilters.USERS in self.activity_stream_filters def is_activity_stream_all_filters(self): return (self.is_activity_stream_tags_filter() and self.is_activity_stream_users_filter()) def get_blocked_users(self): """Return a) users I'm blocking and b) users blocking me. Returns: QuerySet """ return (self.blockers.all() | self.blocked.all()).distinct() @transaction.atomic def block_user(self, user): """Blocks this user. Any following relationships are also removed. Args: user (User) """ self.blocked.add(user) self.following.remove(user) self.followers.remove(user) @notify def notify_on_join(self, community): """Returns notification to all other current members that this user has just joined the community. Args: community (Community) Returns: list: list of Notification instances """ return [ Notification( content_object=self, actor=self, recipient=member, community=community, verb="new_member", ) for member in community.members.exclude(pk=self.pk) ] @notify def notify_on_follow(self, recipient, community): """Sends notification to recipient that they have just been followed. Args: recipient (User) community (Community) Returns: Notification """ return Notification( content_object=self, actor=self, recipient=recipient, community=community, verb="new_follower", ) @notify def notify_on_update(self): """Sends notification to followers that user has updated their profile. This is sent to followers across all communities where the user is an active member. If follower belongs to multiple common communities, we just send notification to one community. We only send notifications if certain tracked fields are updated e.g. bio or avatar. Returns: list: Notifications to followers """ if self.has_tracker_changed(): return takefirst( [ Notification( content_object=self, actor=self, recipient=follower, community=membership.community, verb="update", ) for membership in self.membership_set.filter( active=True).select_related("community") for follower in self.followers.for_community(membership.community) ], lambda n: n.recipient, ) def get_email_addresses(self): """Get set of emails belonging to user. Returns: set: set of email addresses """ return set([self.email]) | set( self.emailaddress_set.values_list("email", flat=True)) def dismiss_notice(self, notice): """ Adds notice permanently to list of dismissed notices. Args: notice (str): unique notice ID e.g. "private-stash" """ if notice not in self.dismissed_notices: self.dismissed_notices.append(notice) self.save(update_fields=["dismissed_notices"])
class Location(models.Model): """Single location (Point) on map. This model is often referred by other models for establishing a spatial context. """ location_id = models.CharField(max_length=100) name = models.CharField(max_length=255, null=True, blank=True) date_created = models.DateTimeField(blank=True, editable=False) description = models.TextField(null=True, blank=True) is_public = models.BooleanField(default=False) coordinates = models.PointField(srid=4326) timezone = TimeZoneField(default=timezone.get_default_timezone_name()) owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='owned_locations') managers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='managed_locations', blank=True) country = models.CharField(max_length=100, blank=True, null=True, editable=False) state = models.CharField(max_length=100, blank=True, null=True, editable=False) county = models.CharField(max_length=100, blank=True, null=True, editable=False) city = models.CharField(max_length=100, blank=True, null=True, editable=False) research_project = models.ForeignKey('research.ResearchProject', blank=True, null=True) objects = LocationManager() class Meta: unique_together = ['research_project', 'location_id'] @property def get_x(self): return self.coordinates.x @property def get_y(self): return self.coordinates.y @property def owner_name(self): return self.owner.username @property def latlng(self): return "{}, {}".format(self.coordinates.y, self.coordinates.x) def can_view(self, user=None): """Determines whether user can view the location. :param user: user for which the test is made :type user: :py:class:`django.contrib.auth.User` :return: True if user can see the location, False otherwise :rtype: bool """ user = user or get_current_user() return (self.is_public or user == self.owner or user in self.managers.all()) def can_update(self, user=None): user = user or get_current_user() return (user == self.owner or user in self.managers.all()) def can_delete(self, user=None): user = user or get_current_user() return user == self.owner def save(self, **kwargs): if self.date_created is None: self.date_created = timezone.now() old_instance = None if self.pk: old_instance = Location.objects.get(pk=self.pk) if (settings.REVERSE_GEOCODING and (not self.pk or (old_instance and self.coordinates != old_instance.coordinates))): self.reverse_geocoding() super(Location, self).save(**kwargs) if old_instance and self.coordinates != old_instance.coordinates: for deployment in self.deployments.all(): for resource in deployment.resources.all(): resource.refresh_collection_bbox() def reverse_geocoding(self, set_fields=True, return_data=False): """ using pygeocoder: http://code.xster.net/pygeocoder/wiki/Home update fields: .. code-block:: * city * county * state * country In case of `GeocoderException` required fields will be set to empty. """ parsed_data = { 'city': '', 'county': '', 'state': '', 'country': '', } try: results = Geocoder.reverse_geocode(self.coordinates.y, self.coordinates.x) except GeocoderError: pass else: for k in results.data[0]['address_components']: if 'locality' in k['types']: parsed_data['city'] = k['long_name'] if 'administrative_area_level_2' in k['types']: parsed_data['county'] = k['long_name'] if 'administrative_area_level_1' in k['types']: parsed_data['state'] = k['long_name'] if 'country' in k['types']: parsed_data['country'] = k['long_name'] if set_fields: for key, value in parsed_data.items(): setattr(self, key, value) if return_data: return parsed_data def __unicode__(self): return unicode("Location: %s" % (self.location_id, )) class Meta: ordering = ['-pk']
class User(AbstractUser): """User object User object, as defined and customized for project implementation. TODO: Document common patterns for User customization. """ objects = UserManager() # First Name and Last Name do not cover name patterns # around the globe. name = models.CharField(_('Name of User'), blank=True, max_length=255) role = models.ForeignKey(Role, blank=True, null=True, on_delete=CASCADE) organization = models.CharField(_('Organization'), max_length=100, blank=True, null=True) timezone = TimeZoneField(blank=True, null=True) photo = models.ImageField(upload_to='photo', max_length=100, blank=True, null=True, storage=get_media_file_storage(folder='media')) class Meta(object): ordering = ('username',) def __str__(self): return self.get_full_name() @property def is_admin(self): return self.role.is_admin @property def is_manager(self): return self.role.is_manager @property def is_reviewer(self): return self.role.is_reviewer def _fire_saved(self, old_instance=None): signals.user_saved.send(self.__class__, user=None, instance=self, old_instance=old_instance) def save(self, *args, **kwargs): if self.role is None: self.role = Role.objects.filter(is_admin=False, is_manager=False).last() \ or Role.objects.first() old_instance = User.objects.filter(pk=self.pk).first() res = super().save(*args, **kwargs) with transaction.atomic(): transaction.on_commit(lambda: self._fire_saved(old_instance)) return res def can_view_document(self, document): # TODO: review with new user access strategies # allow to any "power" user is_able = self.is_superuser or self.is_admin or self.is_manager # project-level perm. for reviewers if not is_able and self.is_reviewer: is_able = document.project.reviewers.filter(pk=self.pk).exists() # task-queue-level perm. for reviewers if not is_able and self.is_reviewer: is_able = self.taskqueue_set.filter(documents=document).exists() return is_able def get_full_name(self): """ Returns the first_name plus the last_name, with a space in between or username """ if self.name: return self.name if self.first_name and self.last_name: full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() return self.username def get_time_zone(self): return self.timezone or tzlocal.get_localzone()
class Member(models.Model): mid = models.CharField(max_length=12) real_name = models.CharField(max_length=60) tz = TimeZoneField(default='Europe/London')
class Group(BaseModel, LocationModel, ConversationMixin): name = models.CharField(max_length=settings.NAME_MAX_LENGTH, unique=True) description = models.TextField(blank=True) members = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='groups', through='GroupMembership') password = models.CharField(max_length=255, blank=True) # TODO remove soon public_description = models.TextField(blank=True) application_questions = models.TextField(blank=True) status = models.CharField( default=GroupStatus.ACTIVE.value, choices=[(status.value, status.value) for status in GroupStatus], max_length=100, ) sent_summary_up_to = DateTimeField(null=True) timezone = TimeZoneField(default='Europe/Berlin', null=True, blank=True) active_agreement = models.OneToOneField( 'groups.Agreement', related_name='active_group', null=True, on_delete=models.SET_NULL ) last_active_at = DateTimeField(default=tz.now) is_open = models.BooleanField(default=False) def __str__(self): return 'Group {}'.format(self.name) def add_member(self, user, history_payload=None): membership = GroupMembership.objects.create(group=self, user=user) History.objects.create( typus=HistoryTypus.GROUP_JOIN, group=self, users=[ user, ], payload=history_payload ) return membership def remove_member(self, user): History.objects.create( typus=HistoryTypus.GROUP_LEAVE, group=self, users=[ user, ] ) GroupMembership.objects.filter(group=self, user=user).delete() def is_member(self, user): return not user.is_anonymous and GroupMembership.objects.filter(group=self, user=user).exists() def is_member_with_role(self, user, role_name): return not user.is_anonymous and GroupMembership.objects.filter( group=self, user=user, roles__contains=[role_name] ).exists() def is_playground(self): return self.status == GroupStatus.PLAYGROUND.value def accept_invite(self, user, invited_by, invited_at): self.add_member( user, history_payload={ 'invited_by': invited_by.id, 'invited_at': invited_at.isoformat(), 'invited_via': 'e-mail' } ) def refresh_active_status(self): self.last_active_at = tz.now() if self.status == GroupStatus.INACTIVE.value: self.status = GroupStatus.ACTIVE.value self.save() def has_recent_activity(self): return self.last_active_at >= tz.now() - timedelta(days=settings.NUMBER_OF_DAYS_UNTIL_GROUP_INACTIVE) def get_application_questions_or_default(self): return self.application_questions or self.get_application_questions_default() def get_application_questions_default(self): return render_to_string('default_application_questions.nopreview.jinja2', { 'group': self, })
class Userdetail(models.Model): userid = models.CharField(max_length=10) real_name = models.CharField(max_length=20) tz = TimeZoneField(default='Europe/London')
class GCCUser(AbstractUser, AddressableModel): @staticmethod def upload_seed(instance): return 'prologinuser/{}'.format(instance.pk).encode() # user have to be imported by the oauth client id = models.IntegerField(primary_key=True) USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] gender = GenderField(blank=True, null=True, db_index=True) school_stage = EnumField( EducationStage, null=True, db_index=True, blank=True, verbose_name=_("Educational stage"), ) phone = models.CharField(max_length=16, blank=True, verbose_name=_("Phone")) birthday = models.DateField(blank=True, null=True, verbose_name=_("Birth day")) allow_mailing = models.BooleanField( default=True, blank=True, db_index=True, verbose_name=_("Allow Girls Can Code! to send me emails"), help_text=_( "We only mail you to provide useful information during the " "various stages of the contest. We hate spam as much as " "you do!"), ) signature = models.TextField(blank=True, verbose_name=_("Signature")) timezone = TimeZoneField(default=settings.TIME_ZONE, verbose_name=_("Time zone")) preferred_locale = models.CharField( max_length=8, blank=True, verbose_name=_("Locale"), choices=settings.LANGUAGES, ) @cached_property def participations_count(self): applicants = Applicant.objects.filter(user=self) return sum((applicant.status == ApplicantStatusTypes.confirmed.value) for applicant in applicants) @property def unsubscribe_token(self): user_id = str(self.id).encode() secret = settings.SECRET_KEY.encode() return hashlib.sha256(user_id + secret).hexdigest() def has_partial_address(self): return any((self.address, self.city, self.country, self.postal_code)) def has_complete_address(self): return all((self.address, self.city, self.country, self.postal_code)) def has_complete_profile(self): return self.has_complete_address() and all(( self.first_name, self.last_name, self.email, self.gender, self.birthday, self.phone, )) def get_absolute_url(self): return reverse('users:profile', args=[self.pk]) def get_unsubscribe_url(self): return '{}{}?uid={}&token={}'.format( settings.SITE_BASE_URL, reverse('users:unsubscribe'), self.id, self.unsubscribe_token, )
class Site(PrimaryModel, StatusModel): """ A Site represents a geographic location within a network; typically a building or campus. The optional facility field can be used to include an external designation, such as a data center name (e.g. Equinix SV6). """ name = models.CharField(max_length=100, unique=True) _name = NaturalOrderingField(target_field="name", max_length=100, blank=True) slug = models.SlugField(max_length=100, unique=True) region = models.ForeignKey( to="dcim.Region", on_delete=models.SET_NULL, related_name="sites", blank=True, null=True, ) tenant = models.ForeignKey( to="tenancy.Tenant", on_delete=models.PROTECT, related_name="sites", blank=True, null=True, ) facility = models.CharField(max_length=50, blank=True, help_text="Local facility ID or description") asn = ASNField( blank=True, null=True, verbose_name="ASN", help_text="32-bit autonomous system number", ) time_zone = TimeZoneField(blank=True) description = models.CharField(max_length=200, blank=True) physical_address = models.CharField(max_length=200, blank=True) shipping_address = models.CharField(max_length=200, blank=True) latitude = models.DecimalField( max_digits=8, decimal_places=6, blank=True, null=True, help_text="GPS coordinate (latitude)", ) longitude = models.DecimalField( max_digits=9, decimal_places=6, blank=True, null=True, help_text="GPS coordinate (longitude)", ) contact_name = models.CharField(max_length=50, blank=True) contact_phone = models.CharField(max_length=20, blank=True) contact_email = models.EmailField(blank=True, verbose_name="Contact E-mail") comments = models.TextField(blank=True) images = GenericRelation(to="extras.ImageAttachment") csv_headers = [ "name", "slug", "status", "region", "tenant", "facility", "asn", "time_zone", "description", "physical_address", "shipping_address", "latitude", "longitude", "contact_name", "contact_phone", "contact_email", "comments", ] clone_fields = [ "status", "region", "tenant", "facility", "asn", "time_zone", "description", "physical_address", "shipping_address", "latitude", "longitude", "contact_name", "contact_phone", "contact_email", ] class Meta: ordering = ("_name", ) def __str__(self): return self.name def get_absolute_url(self): return reverse("dcim:site", args=[self.slug]) def to_csv(self): return ( self.name, self.slug, self.get_status_display(), self.region.name if self.region else None, self.tenant.name if self.tenant else None, self.facility, self.asn, self.time_zone, self.description, self.physical_address, self.shipping_address, self.latitude, self.longitude, self.contact_name, self.contact_phone, self.contact_email, self.comments, )
def test_some_positional_args_ok(self): TimeZoneField('a verbose name', 'a name', True)
class LilyUser(TenantMixin, PermissionsMixin, AbstractBaseUser): """ A custom user class implementing a fully featured User model with admin-compliant permissions. Password and email are required. Other fields are optional. """ first_name = models.CharField(_('first name'), max_length=45) preposition = models.CharField(_('preposition'), max_length=100, blank=True) last_name = models.CharField(_('last name'), max_length=45) email = models.EmailField(_('email address'), max_length=255, unique=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_( 'Designates whether the user can log into this admin site.')) is_active = models.BooleanField( _('active'), default=True, help_text= _('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.' )) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) lily_groups = models.ManyToManyField( LilyGroup, verbose_name=_('Lily groups'), blank=True, related_name='user_set', related_query_name='user', ) phone_number = models.CharField(_('phone number'), max_length=40, blank=True) social_media = models.ManyToManyField( SocialMedia, blank=True, verbose_name=_('list of social media')) language = models.CharField(_('language'), max_length=3, choices=settings.LANGUAGES, default='en') timezone = TimeZoneField(default='Europe/Amsterdam') primary_email_account = models.ForeignKey('email.EmailAccount', blank=True, null=True) objects = LilyUserManager() EMAIL_TEMPLATE_PARAMETERS = [ 'first_name', 'preposition', 'last_name', 'full_name', 'twitter', 'linkedin', 'phone_number', 'current_email_address', 'user_group' ] USERNAME_FIELD = 'email' REQUIRED_FIELDS = [ 'first_name', 'last_name', ] def get_absolute_url(self): """ Get the url to the user page """ return reverse('dashboard') @property def full_name(self): return self.get_full_name() def get_full_name(self): """ Return full name of this user without unnecessary white space. """ if self.preposition: return u' '.join( [self.first_name, self.preposition, self.last_name]).strip() return u' '.join([self.first_name, self.last_name]).strip() def get_short_name(self): """ Returns the short name for the user. """ return self.first_name def email_user(self, subject, message, from_email=None): """ Sends an email to this User. """ send_mail(subject, message, from_email, [self.email]) @property def twitter(self): try: twitter = self.social_media.filter(name='twitter').first() except SocialMedia.DoesNotExist: pass else: return twitter.username @property def linkedin(self): try: linkedin = self.social_media.filter(name='linkedin').first() except SocialMedia.DoesNotExist: pass else: return linkedin.profile_url @property def user_group(self): user_group = self.lily_groups.first() if not user_group: return '' return user_group def __unicode__(self): return self.get_full_name() or unicode(self.get_username()) class Meta: verbose_name = _('user') verbose_name_plural = _('users') ordering = ['first_name', 'last_name'] permissions = (("send_invitation", _("Can send invitations to invite new users")), )
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)
class UserDetail(AbstractUser): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) tz = TimeZoneField()
def test_invalid_choices_display(self): self.assertRaises(ValueError, lambda: TimeZoneField(choices_display='invalid'))
class Doctor(models.Model): MEDECINE_CHOICES = settings.MEDECINE_CHOICES TITLE_CHOICES = settings.TITLE_CHOICES phone_regex = RegexValidator( regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'." " Up to 15 digits allowed.") title = models.IntegerField(verbose_name=_(u'Title'), choices=TITLE_CHOICES) picture = models.ImageField(upload_to=settings.IMAGE_UPLOAD_PATH, blank=True, null=True) speciality = models.IntegerField(verbose_name=_(u'Speciality'), choices=MEDECINE_CHOICES) slug = models.SlugField(verbose_name=_(u'slug'), max_length=50, blank=True) vat_number = models.CharField(verbose_name=_(u'VAT number'), max_length=20, blank=True) telephone = models.CharField(verbose_name=_(u'Telephone'), validators=[phone_regex], help_text=_(u'format : +32475123456'), max_length=20, blank=True) address = AddressField() start_time = models.TimeField(verbose_name=_(u'Start time'), blank=False, default="09:00") end_time = models.TimeField(verbose_name=_(u'End time'), blank=False, default="18:00") colorslots = models.ManyToManyField(ColorSlot, verbose_name=_(u'ColorSlot'), blank=True) view_busy_slot = models.BooleanField(verbose_name=_(u'Can see busy slots'), default=True) view_in_list = models.BooleanField( verbose_name=_(u'Can see in doctors list'), default=True) weektemplate = models.ForeignKey(WeekTemplate, verbose_name=_(u'Week template'), blank=True, null=True) slots = models.ManyToManyField(Slot, verbose_name=_(u'Slots'), blank=True) confirm = models.TextField(verbose_name=_(u'Confirm key'), blank=True, null=True) text_rdv = models.TextField(verbose_name=_(u'Text RDV'), blank=True, null=True) text_horaires = models.TextField(verbose_name=_(u'Text horaires'), blank=True, null=True) timezone = TimeZoneField(default=settings.TIME_ZONE) invoices = models.ManyToManyField(Invoice, verbose_name=_(u'Invoices'), blank=True) can_recharge = models.BooleanField( verbose_name=_(u'Can recharge type of subscription '), blank=False, null=False, default=True) refer_userprofile = models.ForeignKey('users.UserProfile', verbose_name=_('UserProfile'), related_name='refer_userprofile', null=True, blank=True) def __str__(self): return u"%s " % self.slug def get_title(self): return u"%s" % self.TITLE_CHOICES[self.title - 1][1] def get_speciality(self): return u"%s" % self.MEDECINE_CHOICES[self.speciality - 1][1] def full_name(self): return u"%s %s" % (self.get_title(), self.real_name()) def real_name(self): if self.refer_userprofile is not None: return u"%s %s" % (self.refer_userprofile.user.first_name, self.refer_userprofile.user.last_name) else: return None def get_n_colorslots(self): return len(self.colorslots.all()) def get_colorslot(self, i): ret = None if self.get_n_colorslots() > 0: for cs in self.colorslots.all(): if cs.slot == i: ret = cs if ret is None: ret = ColorSlot(slot=i, free_slot_color=settings.SLOT_COLOR[i - 1]) ret.save() self.colorslots.add(ret) self.save() return ret def set_slug(self): if not self.slug: if self.refer_userprofile is not None: self.slug = slugify(self.real_name()) def get_all_slottemplates(self): out = [] if len(self.get_weektemplate().days.all()) > 0: for dt in self.get_weektemplate().days.all(): for s in dt.slots.all(): out.append(s.as_json(dt.day, self)) return out def remove_all_slottemplates(self): self.get_weektemplate().remove_all_slottemplates() def get_weektemplate(self): if not self.weektemplate: wk = WeekTemplate() wk.save() self.weektemplate = wk self.save() return self.weektemplate def get_daytemplate(self, i): return self.get_weektemplate().get_daytemplate(i) def get_color(self, i, booked): slot = self.get_colorslot(i) return str(slot.booked_slot_color) if booked else str( slot.free_slot_color) def get_active_invoice(self): if len(self.invoices.all()): for i in self.invoices.filter(active=True): if i.active: return i else: return None def already_use_free_invoice(self): out = False if len(self.invoices.all()): for i in self.invoices.all(): if i.price_exVAT == 0: out = True return out def save(self, *args, **kwargs): super(Doctor, self).save(*args, **kwargs) adr = self.address.raw.replace(' ', '+') response = requests.get( 'https://maps.googleapis.com/maps/api/geocode/json?address=%s' % adr) resp_json_payload = response.json() sol = resp_json_payload['results'][0] dic_address = {} for d in sol['address_components']: dic_address[d['types'][0]] = d country = Country.objects.filter( code=dic_address['country']['short_name']) if len(country): country = country[0] else: country = Country(code=dic_address['country']['short_name'], name=dic_address['country']['long_name']) country.save() state = State.objects.filter( country=country, name=dic_address['administrative_area_level_1']['long_name']) if len(state): state = state[0] else: state = State( country=country, code=dic_address['administrative_area_level_1']['short_name'], name=dic_address['administrative_area_level_1']['long_name']) state.save() locality = Locality.objects.filter( state=state, postal_code=dic_address['postal_code']['long_name'], name=dic_address['locality']['long_name']) if len(locality): locality = locality[0] else: locality = Locality( state=state, postal_code=dic_address['postal_code']['long_name'], name=dic_address['locality']['long_name']) locality.save() self.address.locality = locality self.address.latitude = sol['geometry']['location']['lat'] self.address.longitude = sol['geometry']['location']['lng'] self.address.route = dic_address['route']['long_name'] self.address.street_number = dic_address['street_number']['long_name'] self.address.save() super(Doctor, self).save(*args, **kwargs) def as_json(self): return { 'doctor': self.full_name(), 'lat': self.address.latitude, 'link': '/doc/%s/' % self.slug, 'lng': self.address.longitude, 'spec': self.get_speciality(), 'locality': self.address.locality.name, 'img': self.picture if self.picture else None }
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()
class EventDate(TimestampedModel): """ Django models in relation with Event Models (Event Date) Start / End Date - Price/Cost / featured """ event = models.ForeignKey('Event', related_name='event_dates', verbose_name='Your Event') location_name = models.CharField(max_length=250, default='', null=True, blank=True) latitude = models.FloatField(default=0.00) longitude = models.FloatField(default=0.00) address_1 = models.CharField(max_length=100, default='', null=True, blank=True) address_2 = models.CharField(max_length=100, default='', null=True, blank=True) country = CountryField(null=True, blank=True) city = models.CharField(max_length=100, null=True, blank=True) state = models.CharField(max_length=50, null=True, blank=True) region = models.CharField(max_length=50, null=True, blank=True) zipcode = models.CharField(max_length=20, null=True, blank=True) timezone = TimeZoneField(null=False, blank=False, default='UTC') start_date = models.DateTimeField(null=True, blank=False) end_date = models.DateTimeField(null=True, blank=True) feature_headline = models.CharField(max_length=100) feature_detail = models.TextField() currency = models.ForeignKey(Currency, null=True, blank=True) attend_free = models.BooleanField(default=False) exhibit_free = models.BooleanField(default=False) attend_price_from = models.FloatField( default=0.0, verbose_name='Attend Price US$ (From)') # price range from (attend) attend_price_to = models.FloatField( default=0.0, verbose_name='Attend Price US$ (To)') # price range from (attend) exhibit_price_from = models.FloatField( default=0.0, verbose_name='Exhibit Price US$ (From)') # price range from (exhibit) exhibit_price_to = models.FloatField( default=0.0, verbose_name='Exhibit Price US$ (To)') # price range from (exhibit) shared = models.ManyToManyField(UserProfile, null=True, blank=True, related_name='shared_dates') class Meta: ordering = ('-start_date', ) def __unicode__(self): return u'{} - {}'.format(self.event.title.capitalize(), self.get_date_display()) def normalize_time(self, time): tz = pytz.timezone(self.timezone.zone) return time.astimezone(tz) def get_date_display(self): tz_start_date = self.normalize_time(self.start_date) tz_end_date = self.normalize_time(self.end_date) if tz_start_date.date() == tz_end_date.date(): date = get_time_display(tz_start_date) else: date = u"{} - {}".format(get_time_display(tz_start_date), get_time_display(tz_end_date)) return date