class Page(models.Model): title = models.CharField( max_length=100, verbose_name='título', ) summary = models.TextField(verbose_name='resumen para el header', ) image = models.ImageField( max_length=256, upload_to='cms/page/', verbose_name='imagen del banner principal', help_text='El tamano de la imágen debe ser 1920x469px', ) list_image = models.ImageField( max_length=256, upload_to='cms/page-list/', verbose_name='imagen para listados', help_text='El tamano de la imágen debe ser 320x410px', ) parent = models.ForeignKey( 'self', on_delete=models.CASCADE, verbose_name='página padre', blank=True, null=True, related_name='children', ) slug = AutoSlugField( populate_from='title', unique=True, ) position = models.PositiveIntegerField(verbose_name='posición', ) content = models.TextField( verbose_name='contenido de la página', blank=True, ) has_children_accordion = models.BooleanField( verbose_name='muestra menú lateral', default=False, ) show_children = models.BooleanField( verbose_name='mostrar hijos', default=True, ) footer_title_1 = models.CharField( verbose_name='Título 1', max_length=100, blank=True, null=True, ) footer_link_1 = models.CharField( verbose_name='Enlace interno 1', max_length=256, blank=True, null=True, ) footer_image_1 = models.ImageField( verbose_name='Imagen 1', blank=True, null=True, ) footer_title_2 = models.CharField( verbose_name='Título 2', max_length=100, blank=True, null=True, ) footer_link_2 = models.CharField( verbose_name='Enlace interno 2', max_length=256, blank=True, null=True, ) footer_image_2 = models.ImageField( verbose_name='Imagen 2', blank=True, null=True, ) footer_title_3 = models.CharField( verbose_name='Título 3', max_length=100, blank=True, null=True, ) footer_link_3 = models.CharField( verbose_name='Enlace interno 3', max_length=256, blank=True, null=True, ) footer_image_3 = models.ImageField( verbose_name='Imagen 3', blank=True, null=True, ) meta_title = models.CharField( max_length=55, help_text='Máximo 55 caracteres', ) meta_description = models.CharField( max_length=115, help_text='Máximo 115 caracteres', ) created_at = models.DateTimeField(default=timezone.now, ) updated_at = models.DateTimeField( verbose_name='actualizado en', default=now, ) is_active = models.BooleanField( verbose_name='¿está activa?', default=False, ) video_url = models.URLField( blank=True, verbose_name='url de video de la página', help_text='Debe ser un video de youtube. Ej: https://www.youtube.com/' 'watch?v=697EbMJei_Q', ) @property def embed_video_url(self): from product.utils import embed_video return embed_video(self.video_url) def clean(self): depth = 0 parent = self.parent while parent is not None: depth += 1 parent = parent.parent if depth >= 4: raise ValidationError( 'No se puede crear más de 4 niveles de páginas', ) if self.show_children and strip_tags(self.content): raise ValidationError({ 'content': '''Si muestra los hijos no puede tener texto en el contenido''' }) @property def get_parents(self): parents = [] parent = self.parent while parent: parents.append(parent) parent = parent.parent parents.reverse() return parents @property def get_image(self): if self.image: return self.image parent = self.parent while parent.image or parent: if parent.image: return parent.image else: parent = parent.parent return self.parent.image def get_absolute_url(self): return reverse('cms:page_detail', args=(self.slug, )) # Search Mixin @property def get_search_result_title(self): return self.title @property def get_search_result_description(self): return self.summary @property def get_search_result_image(self): return self.list_image or self.image @property def has_footer(self): return (self.footer_title_1 and self.footer_link_1 and self.footer_image_1 and self.footer_title_2 and self.footer_link_2 and self.footer_image_2 and self.footer_title_3 and self.footer_link_3 and self.footer_image_3) def __str__(self): return '{0}'.format(self.title) class Meta: verbose_name = 'página' verbose_name_plural = 'páginas' unique_together = ('position', 'parent') ordering = ( '-parent_id', 'position', )
class Story(models.Model): title = models.CharField('Headline', max_length=255) slug = AutoSlugField(populate_from='title', unique=True) subheader = models.CharField('Subheader', max_length=255, blank=True) state = FSMField(default='new', protected=False, db_index=True, state_choices=STATE_CHOICES) highlight = models.BooleanField( default=False, db_index=True, help_text='If checked, this story will be listed towards the top') organization = models.ForeignKey('datasets.Publisher', blank=True, null=True) datasets = models.ManyToManyField('datasets.CatalogRecord', blank=True, related_name='linked_stories') datasource_urls = models.TextField(blank=True, help_text=''' Extra datasource urls ''') #related is spanned through concepts #related = models.ManyToManyField('self', blank=True) tags = TaggableManager('User tags', blank=True) concepts = models.ManyToManyField('focus.Concept', blank=True) repostPermissionLine = models.CharField( verbose_name='Reposted with Permission Text', max_length=255, blank=True) bodyFeaturedText = RichTextField(verbose_name='Body Featured Text', blank=True) body = RichTextUploadingField() author = models.CharField(max_length=255, blank=True) posted_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='posted_stories') approved_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, related_name='approved_stories') published_at = models.DateTimeField( null=True, blank=True) #TODO url based on this or author? created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) featured_image = models.ImageField(upload_to='stories', blank=True) card_image = ImageRatioField('featured_image', '400x400') wide_card_image = ImageRatioField('featured_image', '800x400') featured_image_caption = models.TextField(blank=True) objects = StoryManager() def __str__(self): if len(self.title) > 53: return self.title[:50] + '...' return self.title class Meta: verbose_name_plural = 'stories' ordering = ['-highlight', '-published_at'] @property def authored_by(self): return self.author or self.posted_by def get_absolute_url(self): return reverse('stories:detail', args=(self.slug, )) @transition(field=state, source=['new', 'rejected'], target='published') def publish(self): if not self.published_at: self.published_at = datetime.now() @transition(field=state, source='new', target='rejected') def reject(self): pass @lru_cache() def related_concepts(self): return self.concepts.all().get_descendants( include_self=True).search_matched() def related_stories(self): concepts = self.related_concepts() stories = Story.objects.published().filter( concepts__in=concepts).exclude(pk=self.pk).distinct() return stories def related_datasets(self): from apps.datasets.models import CatalogRecord concepts = self.related_concepts() return CatalogRecord.objects.published().filter(concepts__in=concepts) def map_datasource_urls(self): #TODO datasource is more broad then datasets, handle those as suggestions from apps.datasets.models import DatasetURL unmapped_urls = list() new_datasets = list() for ds_url in self.datasource_urls.split(): if '://' not in ds_url: continue print('Story datasource:', ds_url) du = DatasetURL.objects.get_or_create(url=ds_url)[0] if du.catalog_record: self.datasets.add(du.catalog_record) new_datasets.append(du.catalog_record) else: cr = du.attempt_catalog_record_sync() if cr: self.datasets.add(cr) new_datasets.append(cr) else: unmapped_urls.append(du) self.datasource_urls = '\n'.join((du.url for du in unmapped_urls)) self.save() return new_datasets
class FormEntry(models.Model): """Form entry.""" user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE) name = models.CharField(_("Name"), max_length=255) title = models.CharField(_("Title"), max_length=255, null=True, blank=True, help_text=_("Shown in templates if available.")) slug = AutoSlugField(populate_from='name', verbose_name=_("Slug"), unique=True) is_public = models.BooleanField( _("Public?"), default=False, help_text=_("Makes your form visible to the public.")) active_date_from = models.DateTimeField( _("Active from"), null=True, blank=True, help_text=_("Date and time when the form becomes active " "in the format: 'YYYY-MM-DD HH:MM'. " "Leave it blank to activate immediately.")) active_date_to = models.DateTimeField( _("Active until"), null=True, blank=True, help_text=_("Date and time when the form becomes inactive " "in the format: 'YYYY-MM-DD HH:MM'. " "Leave it blank to keep active forever.")) inactive_page_title = models.CharField( _("Inactive form page title"), max_length=255, null=True, blank=True, help_text=_("Custom message title to display if form is inactive.")) inactive_page_message = models.TextField( _("Inactive form page body"), null=True, blank=True, help_text=_("Custom message text to display if form is inactive.")) is_cloneable = models.BooleanField( _("Cloneable?"), default=False, help_text=_("Makes your form cloneable by other users.")) # position = models.PositiveIntegerField( # _("Position"), null=True, blank=True # ) success_page_title = models.CharField( _("Success page title"), max_length=255, null=True, blank=True, help_text=_("Custom message title to display after valid form is " "submitted")) success_page_message = models.TextField( _("Success page body"), null=True, blank=True, help_text=_("Custom message text to display after valid form is " "submitted")) action = models.CharField( _("Action"), max_length=255, null=True, blank=True, help_text=_("Custom form action; don't fill this field, unless really " "necessary.")) created = models.DateTimeField(_("Created"), null=True, blank=True, auto_now_add=True) updated = models.DateTimeField(_("Updated"), null=True, blank=True, auto_now=True) is_locked = models.BooleanField( _("Locked against editing?"), default=False, help_text=_("Whether the form entry is locked against editing."), ) class Meta(object): """Meta class.""" verbose_name = _("Form entry") verbose_name_plural = _("Form entries") unique_together = ( ('user', 'slug'), ('user', 'name'), ) def __str__(self): return self.name @property def is_active(self): active_from_ok = True active_to_ok = True now = timezone.now() if self.active_date_from and now < self.active_date_from: active_from_ok = False if self.active_date_to and now > self.active_date_to: active_to_ok = False if active_from_ok and active_to_ok: return True else: return False def get_absolute_url(self): """Get absolute URL. Absolute URL, which goes to the form-entry view view page. :return string: """ return reverse('fobi.view_form_entry', kwargs={'form_entry_slug': self.slug})
class Talk(PonyConfModel): LICENCES = ( ('CC-Zero CC-BY', 'CC-Zero CC-BY'), ('CC-BY-SA', 'CC-BY-SA'), ('CC-BY-ND', 'CC-BY-ND'), ('CC-BY-NC', 'CC-BY-NC'), ('CC-BY-NC-SA', 'CC-BY-NC-SA'), ('CC-BY-NC-ND', 'CC-BY-NC-ND'), ) site = models.ForeignKey(Site, on_delete=models.CASCADE) speakers = models.ManyToManyField(Participant, verbose_name=_('Speakers')) title = models.CharField(max_length=128, verbose_name=_('Talk Title')) slug = AutoSlugField(populate_from='title', unique=True) #abstract = models.CharField(max_length=255, blank=True, verbose_name=_('Abstract')) description = models.TextField(verbose_name=_('Description of your talk')) track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track')) notes = models.TextField( blank=True, verbose_name=_('Message to organizers'), help_text= _('If you have any constraint or if you have anything that may help you to select your talk, like a video or slides of your talk, please write it down here' )) category = models.ForeignKey(TalkCategory, verbose_name=_('Talk Category')) videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True) video_licence = models.CharField(choices=LICENCES, default='CC-BY-SA', max_length=10, verbose_name=_("Video licence")) sound = models.BooleanField(_("I need sound"), default=False) accepted = models.NullBooleanField(default=None) start_date = models.DateTimeField( null=True, blank=True, default=None, verbose_name=_('Beginning date and time')) duration = models.PositiveIntegerField(default=0, verbose_name=_('Duration (min)')) room = models.ForeignKey(Room, blank=True, null=True, default=None) plenary = models.BooleanField(default=False) #materials = models.FileField(null=True, upload_to=talk_materials_destination, verbose_name=_('Materials'), # help_text=_('You can use this field to share some materials related to your intervention.')) token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) conversation = models.OneToOneField(MessageThread) objects = TalkManager() class Meta: ordering = ('title', ) def __str__(self): return self.title def get_speakers_str(self): speakers = list(map(str, self.speakers.all())) if len(speakers) == 0: return 'superman' elif len(speakers) == 1: return speakers[0] else: return ', '.join(speakers[:-1]) + ' & ' + str(speakers[-1]) @property def estimated_duration(self): return self.duration or self.category.duration def get_absolute_url(self): return reverse('talk-details', kwargs={'talk_id': self.token}) @property def end_date(self): if self.estimated_duration: return self.start_date + timedelta(minutes=self.estimated_duration) else: return None @property def dtstart(self): return self.start_date.strftime('%Y%m%dT%H%M%SZ') @property def dtend(self): return self.end_date.strftime('%Y%m%dT%H%M%SZ') @property def dtstamp(self): return self.updated.strftime('%Y%m%dT%H%M%SZ') @property def status(self): return 'CONFIRMED' if self.accepted else 'TENTATIVE' #@property #def materials_name(self): # return basename(self.materials.name) class Meta: ordering = ( 'category__id', 'title', )
class City(models.Model): name = models.CharField('Название города', max_length=100) slug = AutoSlugField(unique=True, populate_from='name') def __str__(self): return self.name
class Post(models.Model): "the class of Post" LIVE_STATUS = 1 DRAFT_STATUS = 2 HIDDEN_STATUS = 3 STATUS_CHOICE = ( (LIVE_STATUS, 'Live'), (DRAFT_STATUS, 'Draft'), (HIDDEN_STATUS, 'Hidden'), ) title = models.CharField(max_length=255, unique=True, help_text=_('Title of the post.')) #slug = models.SlugField(unique_for_date="created_on",unique=True,help_text=_('Short title used in the URLs.')) slug = AutoSlugField(populate_from=lambda instance: instance.title, unique_with=['author__name', 'date_published__month'], slugify=lambda value: value.replace(' ', '-'), always_update=True) #slug=AutoSlugField(unique=True,always_update=True) author = models.ForeignKey(User) status = models.IntegerField(_('status'), choices=STATUS_CHOICE, help_text=_('The status of this news.')) category = models.ForeignKey(Category) img = models.ImageField('Image', upload_to='upload-img', blank=True, null=True) #tag=models.ManyToManyField(Tag,blank=True,null=True,help_text=_('Tags for the post.')) tags = TaggableManager( through=TagPost, blank=True, help_text=_('Tags for the post.Support only En-lang')) content = models.TextField(blank=False) hoter = models.IntegerField(blank=True, default=0) # comments = models.ForeignKey(Comment,blank=True,null=True) created_on = models.DateTimeField(auto_now_add=True, editable=False) date_modified = models.DateTimeField(auto_now_add=True, editable=False) date_published = models.DateTimeField(auto_now_add=True, editable=False) #tags_list=[] # manager #live = LivePostManager() live = LivePostManager() objects = models.Manager() class Meta: ordering = ['-created_on'] def save(self, *args, **kwargs): import datetime #pdb.set_trace() #from blogserver.middleware import threadlocals #if threadlocals.get_current_user(): # self.author = threadlocals.get_current_user() #self. self.created_on = datetime.datetime.now() super(Post, self).save() #pdb.set_trace() @models.permalink def get_absolute_url(self): return ('post_detail', (), { 'year': self.date_published.strftime("%Y"), 'month': self.date_published.strftime("%m").lower(), 'day': self.date_published.strftime("%d"), 'slug': self.slug }) def __unicode__(self): return self.title def serialize_fields(self): """Only these fields will be included in API responses.""" return [ 'id', 'title', 'slug', 'status', 'author', 'tag', 'category', 'img', 'created_on', 'date_published', 'date_modified', 'content', 'comments', 'hoter' ]
class Article(mixins.Seo): title = models.CharField(_('title'), max_length=255) slug = AutoSlugField(_('slug'), populate_from='title', unique_with='category', editable=True, blank=True) published = models.BooleanField(_('published'), default=True) order = models.PositiveSmallIntegerField(_('order'), default=0) language = LanguageField(_('language')) category = models.ForeignKey(Category, verbose_name=_('category'), related_name='_articles', null=True, blank=True) intro = models.TextField(_('intro'), blank=True, default='') content = models.TextField(_('content'), blank=True, default='') thumbnail = FileBrowseField(_('thumbnail'), help_text=_('Displayed in category view.'), max_length=255, null=True, blank=True) show_created = models.BooleanField(_('show creation date'), default=False) show_breadcrumbs = models.BooleanField(_('show breadcrumbs'), default=True) show_back_link = models.BooleanField(_('show back link'), default=True) images_thumbnails_size = FilebrowserVersionField( _('images thumbnails size'), allow_null=True) created = models.DateTimeField(_('creation date'), default=timezone.now) last_modified = models.DateTimeField(_('last modification date'), auto_now=True) class Meta: app_label = 'content' ordering = ('language', 'category', 'order', 'title') verbose_name = _('article') verbose_name_plural = _('articles') def __str__(self): return self.title @property def route(self): if not self.published: return None category = self.category if category: if category.route: if category.route == '/': category_route = '' else: category_route = category.route return '{}/{}'.format(category_route, self.slug) page = self.pages.filter(published=True).first() if page: return page.route return None @property def all_routes(self): if not self.published: return [] routes = [] if self.route: routes.append(self.route) for page in self.pages.filter(published=True): if page.route not in routes: routes.append(page.route) return routes @property def meta(self): return utils.generate_meta( title=self.meta_title_override or self.title, title_suffix=self.meta_title_site_name_suffix, description=self.meta_description_override, robots=self.meta_robots_override) @property def breadcrumbs(self): output = [{'name': self.title, 'route': self.route}] parent = self.category while parent: if parent.route: output.append({'name': parent.name, 'route': parent.route}) parent = parent.parent return reversed(output) @property def back_link(self): try: return self.category.route or '/' except AttributeError: return '/' @property def thumbnail_or_first_image(self): try: return self.thumbnail or self.images.filter(published=True) \ .first().image except AttributeError: return None
class FormatVersion(VersionedModel, models.Model): """ Format that a tool identifies. """ uuid = UUIDField(editable=False, unique=True, version=4, help_text=_("Unique identifier")) format = models.ForeignKey( "Format", to_field="uuid", related_name="version_set", null=True, verbose_name=_("the related format"), on_delete=models.CASCADE, ) version = models.CharField(_("version"), max_length=10, null=True, blank=True) pronom_id = models.CharField(_("pronom id"), max_length=32, null=True, blank=True) description = models.CharField( _("description"), max_length=128, null=True, blank=True, help_text=_("Formal name to go in the METS file."), ) access_format = models.BooleanField(_("access format"), default=False) preservation_format = models.BooleanField(_("preservation format"), default=False) slug = AutoSlugField(populate_from="description", unique_with="format", always_update=True) class Meta: verbose_name = _("Format version") ordering = ["format", "description"] def validate_unique(self, *args, **kwargs): super(FormatVersion, self).validate_unique(*args, **kwargs) if len(self.pronom_id) > 0: qs = self.__class__._default_manager.filter( pronom_id=self.pronom_id, enabled=1) if not self._state.adding and self.pk is not None: qs = qs.exclude(pk=self.pk) if qs.exists(): raise ValidationError({ NON_FIELD_ERRORS: [ _("Unable to save, an active Format Version with this pronom id already exists." ) ] }) def __unicode__(self): return _("%(format)s: %(description)s (%(pronom_id)s)") % { "format": self.format, "description": self.description, "pronom_id": self.pronom_id, }
class Institution(models.Model): name = models.CharField( verbose_name='Name', max_length=255, ) slug = AutoSlugField(populate_from='name') abbreviation = models.CharField( verbose_name='Abbreviation', max_length=255, ) url = models.URLField( verbose_name='Url', blank=True, ) year = models.PositiveIntegerField(_('Year'), default=get_default_year_now, validators=[MaxValueValidator(4000)]) country = models.ForeignKey( Country, blank=True, null=True, on_delete=models.PROTECT, ) contact_person = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='institution_contact', verbose_name='Contact person', on_delete=models.SET_NULL, blank=True, null=True, ) memorandum = models.BooleanField(verbose_name='Memorandum signed', default=False) logo = models.ForeignKey('wagtailimages.Image', verbose_name='Logo', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') panels = [ FieldPanel('name'), # FieldPanel('slug'), FieldPanel('abbreviation'), FieldPanel('url'), FieldPanel('year'), FieldPanel('country'), FieldPanel('contact_person'), FieldPanel('memorandum'), ImageChooserPanel('logo'), ] search_fields = [ index.SearchField('name'), index.SearchField('abbreviation'), index.SearchField('content'), index.FilterField('year'), index.FilterField('contact_person'), index.FilterField('country'), ] class Meta: verbose_name = _('Institution') verbose_name_plural = _('Institutions') ordering = ['name'] unique_together = ('name', 'abbreviation', 'country') def __str__(self): return self.name def get_absolute_url(self): return reverse('institutions:detail', args=[self.slug])
class DiscussionTopic(models.Model): """ A topic (thread) associated with a CourseOffering """ offering = models.ForeignKey(CourseOffering, null=False, on_delete=models.PROTECT) title = models.CharField(max_length=140, help_text="A brief description of the topic") content = models.TextField(help_text='The inital message for the topic.') created_at = models.DateTimeField(auto_now_add=True) last_activity_at = models.DateTimeField(auto_now_add=True) message_count = models.IntegerField(default=0) status = models.CharField( max_length=3, choices=TOPIC_STATUSES, default='OPN', help_text= "The topic status: Closed: no replies allowed, Hidden: cannot be seen") pinned = models.BooleanField( default=False, help_text="Should this topic be pinned to bring attention?") author = models.ForeignKey(Member, on_delete=models.PROTECT) def autoslug(self): return make_slug(self.title) slug = AutoSlugField(populate_from='autoslug', null=False, editable=False, unique_with=['offering']) config = JSONField(null=False, blank=False, default=dict) # p.config['markup']: markup language used: see courselib/markup.py # p.config['math']: content uses MathJax? (boolean) # p.config['brushes']: used SyntaxHighlighter brushes (list of strings) -- no longer used with highlight.js defaults = { 'markup': 'creole', 'math': False, } markup, set_markup = getter_setter('markup') math, set_math = getter_setter('math') def save(self, *args, **kwargs): if self.status not in [status[0] for status in TOPIC_STATUSES]: raise ValueError('Invalid topic status') self.content = ensure_sanitary_markup(self.content, self.markup(), restricted=True) new_topic = self.id is None super(DiscussionTopic, self).save(*args, **kwargs) # handle subscriptions if new_topic: subs = DiscussionSubscription.objects.filter( member__offering=self.offering).select_related( 'member__person') for s in subs: s.notify(self) def get_absolute_url(self): return reverse('offering:discussion:view_topic', kwargs={ 'course_slug': self.offering.slug, 'topic_slug': self.slug }) def new_message_update(self): self.last_activity_at = datetime.datetime.now() self.message_count = self.message_count + 1 self.save() def last_activity_at_delta(self): return _time_delta_to_string(self.last_activity_at) def created_at_delta(self): return _time_delta_to_string(self.created_at) def __str___(self): return self.title def html_content(self): "Convert self.content to HTML" return markup_to_html(self.content, self.markup(), offering=self.offering, html_already_safe=True, restricted=True) def still_editable(self): td = datetime.datetime.now() - self.created_at seconds = td.days * 86400 + td.seconds return seconds <= 120 def editable_time_left(self): td = datetime.datetime.now() - self.created_at seconds = td.days * 86400 + td.seconds if seconds > 120: return 'none' minutes, seconds = divmod(120 - seconds, 60) return "%dm:%ds" % (minutes, seconds) def exportable(self): """ Create JSON-serializable representation of topic, for export. """ data = { 'title': self.title, 'body': self.content, 'created_at': self.created_at.isoformat(), 'author': self.author.person.userid, 'status': self.status, 'pinned': self.pinned } messages = DiscussionMessage.objects.filter( topic=self).select_related('author__person') data['replies'] = [m.exportable() for m in messages] return data
class Player(models.Model): name = models.CharField(max_length=200, unique=True) dota_mmr = models.PositiveIntegerField() dota_id = models.CharField(max_length=200, null=True, blank=True) discord_id = models.CharField(max_length=200, null=True, blank=True) slug = AutoSlugField(populate_from='name') ladder_mmr = models.PositiveIntegerField(default=0) score = models.PositiveIntegerField(default=0) rank_ladder_mmr = models.PositiveIntegerField(default=0) rank_score = models.PositiveIntegerField(default=0) voice_issues = models.BooleanField(default=False) bot_access = models.BooleanField(default=False) vouched = models.BooleanField(default=False) blacklist = models.ManyToManyField('self', symmetrical=False, related_name='blacklisted_by') # boundaries for ladder mmr min_allowed_mmr = models.PositiveIntegerField(default=0) max_allowed_mmr = models.PositiveIntegerField(default=0) # ban levels BAN_PLAYING = 1 BAN_PLAYING_AND_LOBBY = 2 BAN_CHOICES = ( (None, "Not banned"), (BAN_PLAYING, 'Banned from playing only'), (BAN_PLAYING_AND_LOBBY, 'Banned from playing and lobby'), ) banned = models.PositiveSmallIntegerField(choices=BAN_CHOICES, null=True, blank=True) new_reg_pings = models.BooleanField(default=False) queue_afk_ping = models.BooleanField(default=True) description = models.CharField(max_length=200, null=True, blank=True) vouch_info = models.CharField(max_length=200, null=True, blank=True) roles = AutoOneToOneField(RolesPreference) objects = PlayerManager() class Meta: ordering = ['rank_ladder_mmr'] def __str__(self): return '%s' % self.name def save(self, *args, **kwargs): # TODO: Move this to clean_fields() later # TODO: (can't do it atm, because of empty dota_id in test data). # TODO: Or even better move this to manager.update_scores() # TODO (this will allow us bulk_update in future) self.score = max(self.score or 0, 0) self.ladder_mmr = max(self.ladder_mmr or 0, 0) created = not self.pk if created: self.roles = RolesPreference.objects.create() super(Player, self).save(*args, **kwargs) # give player initial score and mmr if created: PlayerManager.init_score(self, reset_mmr=True)
class DiscussionMessage(models.Model): """ A message (post) associated with a Discussion Topic """ topic = models.ForeignKey(DiscussionTopic, on_delete=models.CASCADE) content = models.TextField(blank=False) created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) status = models.CharField(max_length=3, choices=MESSAGE_STATUSES, default='VIS') author = models.ForeignKey(Member, on_delete=models.PROTECT) def autoslug(self): return make_slug(self.author.person.userid) slug = AutoSlugField(populate_from='autoslug', null=False, editable=False, unique_with=['topic']) config = JSONField(null=False, blank=False, default=dict) # p.config['markup']: markup language used: see courselib/markup.py # p.config['math']: content uses MathJax? (boolean) # p.config['brushes']: used SyntaxHighlighter brushes (list of strings) -- no longer used with highlight.js defaults = {'math': False, 'markup': 'creole'} math, set_math = getter_setter('math') markup, set_markup = getter_setter('markup') #brushes, set_brushes = getter_setter('brushes') def save(self, *args, **kwargs): if self.status not in [status[0] for status in MESSAGE_STATUSES]: raise ValueError('Invalid topic status') if not self.pk: self.topic.new_message_update() self.content = ensure_sanitary_markup(self.content, self.markup(), restricted=True) new_message = self.id is None super(DiscussionMessage, self).save(*args, **kwargs) # handle subscriptions if new_message: subs = DiscussionSubscription.objects.filter( member__offering=self.topic.offering).select_related( 'member__person') for s in subs: s.notify_message(self) def html_content(self): "Convert self.content to HTML" return markup_to_html(self.content, self.markup(), offering=self.topic.offering, html_already_safe=True, restricted=True) def get_absolute_url(self): return self.topic.get_absolute_url() + '#reply-' + str(self.id) def create_at_delta(self): return _time_delta_to_string(self.created_at) def still_editable(self): td = datetime.datetime.now() - self.created_at seconds = td.days * 86400 + td.seconds return seconds <= 120 def editable_time_left(self): td = datetime.datetime.now() - self.created_at seconds = td.days * 86400 + td.seconds if seconds > 120: return 'none' minutes, seconds = divmod(120 - seconds, 60) return "%dm:%ds" % (minutes, seconds) def was_edited(self): td = self.modified_at - self.created_at return self.modified_at > self.created_at and td > datetime.timedelta( seconds=3) and self.status != 'HID' def exportable(self): """ Create JSON-serializable representation of message, for export. """ data = { 'body': self.content, 'created_at': self.created_at.isoformat(), 'author': self.author.person.userid, 'status': self.status } return data def to_dict(self): return { "author": self.author.person.userid, "topic": self.topic.title, "content": self.content, "visibility": self.get_status_display(), "created": str(self.created_at), "modified": str(self.modified_at) }
class Communication(models.Model): title = models.CharField( max_length=100, verbose_name='título', ) category = models.ForeignKey( 'cms.CommunicationCategory', on_delete=models.CASCADE, verbose_name='categoría', ) image = models.ImageField( max_length=256, upload_to='cms/communication/', verbose_name='imagen del banner principal', help_text='La imagen debe tener el tamaño de 1920x469px', ) list_image = models.ImageField( max_length=256, upload_to='cms/communication-list/', verbose_name='imagen para listados', help_text='El tamano de la imágen debe ser 520x680', ) slug = AutoSlugField( populate_from='title', unique=True, ) content = models.TextField(verbose_name='contenido de la página', ) extended_content = models.TextField( verbose_name='contenido extendido de la página', blank=True, ) event_date = models.DateField( verbose_name='fecha de inicio', blank=True, null=True, ) end_date = models.DateField( verbose_name='fecha de finalización', blank=True, null=True, ) event_hour = models.CharField( max_length=15, verbose_name='hora del evento', blank=True, null=True, ) event_city = models.CharField(max_length=100, verbose_name='sitio del evento', blank=True, null=True) event_place = models.CharField( max_length=100, verbose_name='lugar del evento', blank=True, null=True, ) footer_title_1 = models.CharField( verbose_name='Título 1', max_length=100, blank=True, null=True, ) footer_link_1 = models.CharField( verbose_name='Enlace interno 1', max_length=256, blank=True, null=True, ) footer_image_1 = models.ImageField( verbose_name='Imagen 1', blank=True, null=True, help_text='El tamano de la imágen debe ser 991x122', ) footer_title_2 = models.CharField( verbose_name='Título 2', max_length=100, blank=True, null=True, ) footer_link_2 = models.CharField( verbose_name='Enlace interno 2', max_length=256, blank=True, null=True, ) footer_image_2 = models.ImageField( verbose_name='Imagen 2', blank=True, null=True, help_text='El tamano de la imágen debe ser 991x122', ) footer_title_3 = models.CharField( verbose_name='Título 3', max_length=100, blank=True, null=True, ) footer_link_3 = models.CharField( verbose_name='Enlace interno 3', max_length=256, blank=True, null=True, ) footer_image_3 = models.ImageField( verbose_name='Imagen 3', blank=True, null=True, help_text='El tamano de la imágen debe ser 991x122', ) meta_title = models.CharField( max_length=55, help_text='Máximo 55 caracteres', ) meta_description = models.CharField( max_length=115, help_text='Máximo 115 caracteres', ) is_active = models.BooleanField( verbose_name='está activo', default=True, ) has_contact = models.BooleanField( verbose_name='¿tiene contacto?', default=False, ) created_at = models.DateTimeField(default=timezone.now, ) updated_at = models.DateTimeField( verbose_name='actualizado en', default=now, ) def get_absolute_url(self): return reverse('cms:communication_detail', args=(self.slug, )) # Search Mixin @property def get_search_result_title(self): return self.title @property def get_search_result_description(self): return self.content @property def get_search_result_image(self): return self.list_image or self.image @property def has_footer(self): return (self.footer_title_1 and self.footer_link_1 and self.footer_image_1 and self.footer_title_2 and self.footer_link_2 and self.footer_image_2 and self.footer_title_3 and self.footer_link_3 and self.footer_image_3) def __str__(self): return '{0}'.format(self.title) class Meta: verbose_name = 'comunicación' verbose_name_plural = 'comunicaciones' ordering = ('-created_at', )
class CommunicationCategory(models.Model): title = models.CharField( max_length=100, verbose_name='título', ) slug = AutoSlugField( populate_from='title', unique=True, ) position = models.PositiveIntegerField( verbose_name='posición', unique=True, ) has_comments = models.BooleanField( verbose_name='Comentarios habilitados', default=False, ) has_share_buttons = models.BooleanField( verbose_name='Botones de compartir habilitados', default=False, ) created_at = models.DateTimeField(default=timezone.now, verbose_name='Fecha de creación') footer_title_1 = models.CharField( verbose_name='Título 1', max_length=100, blank=True, null=True, ) footer_link_1 = models.CharField( verbose_name='Enlace interno 1', max_length=256, blank=True, null=True, ) footer_image_1 = models.ImageField( verbose_name='Imagen 1', blank=True, null=True, ) footer_title_2 = models.CharField( verbose_name='Título 2', max_length=100, blank=True, null=True, ) footer_link_2 = models.CharField( verbose_name='Enlace interno 2', max_length=256, blank=True, null=True, ) footer_image_2 = models.ImageField( verbose_name='Imagen 2', blank=True, null=True, ) footer_title_3 = models.CharField( verbose_name='Título 3', max_length=100, blank=True, null=True, ) footer_link_3 = models.CharField( verbose_name='Enlace interno 3', max_length=256, blank=True, null=True, ) footer_image_3 = models.ImageField( verbose_name='Imagen 3', blank=True, null=True, ) @property def has_footer(self): return (self.footer_title_1 and self.footer_link_1 and self.footer_image_1 and self.footer_title_2 and self.footer_link_2 and self.footer_image_2 and self.footer_title_3 and self.footer_link_3 and self.footer_image_3) def get_absolute_url(self): return reverse('cms:communication_by_category_list', args=(self.slug, )) def __str__(self): return '{0}'.format(self.title) class Meta: verbose_name = 'categoría de comunicación' verbose_name_plural = 'categorías de comunicación' ordering = ('position', ) def clean(self): if self.title: unique_case_insensitive(self, 'title')
class RAAppointment(models.Model): """ This stores information about a (Research Assistant)s application and pay. """ person = models.ForeignKey(Person, help_text='The RA who is being appointed.', null=False, blank=False, related_name='ra_person') sin = models.PositiveIntegerField(null=True, blank=True) hiring_faculty = models.ForeignKey( Person, help_text='The manager who is hiring the RA.', related_name='ra_hiring_faculty') unit = models.ForeignKey(Unit, help_text='The unit that owns the appointment', null=False, blank=False) hiring_category = models.CharField(max_length=4, choices=HIRING_CATEGORY_CHOICES, default='GRA') scholarship = models.ForeignKey( Scholarship, null=True, blank=True, help_text='Scholarship associated with this appointment. Optional.') project = models.ForeignKey(Project, null=False, blank=False) account = models.ForeignKey(Account, null=False, blank=False) start_date = models.DateField(auto_now=False, auto_now_add=False) end_date = models.DateField(auto_now=False, auto_now_add=False) pay_frequency = models.CharField(max_length=60, choices=PAY_FREQUENCY_CHOICES, default='B') lump_sum_pay = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Total Pay") lump_sum_hours = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Total Hours", blank=True, null=True) biweekly_pay = models.DecimalField(max_digits=8, decimal_places=2) pay_periods = models.DecimalField(max_digits=6, decimal_places=1) hourly_pay = models.DecimalField(max_digits=8, decimal_places=2) hours = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Biweekly Hours") reappointment = models.BooleanField( default=False, help_text="Are we re-appointing to the same position?") medical_benefits = models.BooleanField( default=False, help_text="50% of Medical Service Plan") dental_benefits = models.BooleanField(default=False, help_text="50% of Dental Plan") notes = models.TextField( blank=True, help_text= "Biweekly emplyment earnings rates must include vacation pay, hourly rates will automatically have vacation pay added. The employer cost of statutory benefits will be charged to the amount to the earnings rate." ) comments = models.TextField(blank=True, help_text="For internal use") offer_letter_text = models.TextField( null=True, help_text= "Text of the offer letter to be signed by the RA and supervisor.") def autoslug(self): if self.person.userid: ident = self.person.userid else: ident = unicode(self.person.emplid) return make_slug(self.unit.label + '-' + unicode(self.start_date.year) + '-' + ident) slug = AutoSlugField(populate_from=autoslug, null=False, editable=False, unique=True) created_at = models.DateTimeField(auto_now_add=True) deleted = models.BooleanField(null=False, default=False) config = JSONField(null=False, blank=False, default={}) # addition configuration stuff defaults = {'use_hourly': False} use_hourly, set_use_hourly = getter_setter('use_hourly') def __unicode__(self): return unicode(self.person) + "@" + unicode(self.created_at) class Meta: ordering = ['person', 'created_at'] def save(self, *args, **kwargs): # set SIN field on the Person object if self.sin and 'sin' not in self.person.config: self.person.set_sin(self.sin) self.person.save() super(RAAppointment, self).save(*args, **kwargs) def default_letter_text(self): """ Default text for the letter (for editing, or use if not set) """ substitutions = { 'start_date': self.start_date.strftime("%B %d, %Y"), 'end_date': self.end_date.strftime("%B %d, %Y"), 'lump_sum_pay': self.lump_sum_pay, 'biweekly_pay': self.biweekly_pay, } if self.pay_frequency == 'B': text = DEFAULT_LETTER_BIWEEKLY else: text = DEFAULT_LETTER_LUMPSUM return '\n\n'.join(text) % substitutions def letter_paragraphs(self): """ Return list of paragraphs in the letter (for PDF creation) """ text = self.offer_letter_text or self.default_letter_text() text = normalize_newlines(text) return text.split("\n\n") @classmethod def semester_guess(cls, date): """ Guess the semester for a date, in the way that financial people do (without regard to class start/end dates) """ mo = date.month if mo <= 4: se = 1 elif mo <= 8: se = 4 else: se = 7 semname = str((date.year - 1900) * 10 + se) return Semester.objects.get(name=semname) @classmethod def start_end_dates(cls, semester): """ First and last days of the semester, in the way that financial people do (without regard to class start/end dates) """ return Semester.start_end_dates(semester) #yr = int(semester.name[0:3]) + 1900 #sm = int(semester.name[3]) #if sm == 1: # start = datetime.date(yr, 1, 1) # end = datetime.date(yr, 4, 30) #elif sm == 4: # start = datetime.date(yr, 5, 1) # end = datetime.date(yr, 8, 31) #elif sm == 7: # start = datetime.date(yr, 9, 1) # end = datetime.date(yr, 12, 31) #return start, end def start_semester(self): "Guess the starting semester of this appointment" start_semester = RAAppointment.semester_guess(self.start_date) # We do this to eliminate hang - if you're starting N days before # semester 1134, you aren't splitting that payment across 2 semesters. start, end = RAAppointment.start_end_dates(start_semester) if end - self.start_date < datetime.timedelta(SEMESTER_SLIDE): return start_semester.next_semester() return start_semester def end_semester(self): "Guess the ending semester of this appointment" end_semester = RAAppointment.semester_guess(self.end_date) # We do this to eliminate hang - if you're starting N days after # semester 1134, you aren't splitting that payment across 2 semesters. start, end = RAAppointment.start_end_dates(end_semester) if self.end_date - start < datetime.timedelta(SEMESTER_SLIDE): return end_semester.previous_semester() return end_semester def semester_length(self): "The number of semesters this contracts lasts for" return self.end_semester() - self.start_semester() + 1
class Repository(Resource, EntityUrlMixin): """Repository.""" ENTITY_TYPES = () LODS = () translatable_fields = ( ("access_conditions", "Access Conditions", "TODO: Help text"), ("buildings", "Buildings", "TODO: Help text"), ("collecting_policies", "Collecting Policies", "TODO: Help text"), ("dates_of_existence", "Dates of Existence", "TODO: Help text"), ("disabled_access", "Disabled Access", "TODO: Help text"), ("finding_aids", "Finding Aids", "TODO: Help text"), ("functions", "Functions", "TODO: Help text"), ("general_context", "General Context", "TODO: Help text"), ("geocultural_context", "Geocultural Context", "TODO: Help text"), ("history", "History", "TODO: Help text"), ("holdings", "Holdings", "TODO: Help text"), ("internal_structures", "Internal Structures", "TODO: Help text"), ("legal_status", "Legal Status", "TODO: Help text"), ("maintenance_notes", "Maintenance Notes", "TODO: Help text"), ("mandates", "Mandates", "TODO: Help text"), ("opening_times", "Opening Times", "TODO: Help text"), ("places", "Places", "TODO: Help text"), ("reproduction_services", "Reproduction Services", "TODO: Help text"), ("research_services", "Research Services", "TODO: Help text"), ("rules", "Rules", "TODO: Help text"), ("sources", "Sources", "TODO: Help text"), ) name = models.CharField("Authorized Form of Name", max_length=255) slug = AutoSlugField(populate_from="name", unique=True) identifier = models.CharField(max_length=255) lod = models.CharField("Level of Description", max_length=255, choices=LODS, blank=True, null=True) type_of_entity = models.CharField("Type of Entity", max_length=255, choices=ENTITY_TYPES, blank=True, null=True) logo = ImageWithThumbsField("Logo", null=True, blank=True, upload_to=lambda inst, fn: os.path.join( inst.slug, "%s_logo%s" % (inst.slug, os.path.splitext(fn)[1])), sizes=settings.THUMBNAIL_SIZES) languages = jsonfield.JSONField(_("Language(s)"), blank=True, default=EMPTY_JSON_LIST) scripts = jsonfield.JSONField(_("Script(s)"), blank=True, default=EMPTY_JSON_LIST) tags = TaggableManager(blank=True) objects = RepositoryManager() class Meta: verbose_name_plural = "repositories" def natural_key(self): return (self.slug, ) @property def primary_contact(self): """Get the main contact property.""" try: return self.contact_set.all().order_by("primary")[0] except IndexError: return None @property def country_code(self): contact = self.primary_contact if contact: return contact.country_code @property def country(self): contact = self.primary_contact if contact and contact.country_code: return utils.country_name_from_code(contact.country_code) def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.slug) def __unicode__(self): return self.name
class Talk(PonyConfModel): LICENCES = ( ('CC-Zero CC-BY', 'CC-Zero CC-BY'), ('CC-BY-SA', 'CC-BY-SA'), ('CC-BY-ND', 'CC-BY-ND'), ('CC-BY-NC', 'CC-BY-NC'), ('CC-BY-NC-SA', 'CC-BY-NC-SA'), ('CC-BY-NC-ND', 'CC-BY-NC-ND'), ) site = models.ForeignKey(Site, on_delete=models.CASCADE) speakers = models.ManyToManyField(Participant, verbose_name=_('Speakers')) title = models.CharField(max_length=128, verbose_name=_('Talk Title')) slug = AutoSlugField(populate_from='title', unique=True) description = models.TextField( verbose_name=_('Description of your talk'), help_text=_('This description will be visible on the program.')) track = models.ForeignKey(Track, blank=True, null=True, verbose_name=_('Track'), on_delete=models.SET_NULL) tags = models.ManyToManyField(Tag, blank=True) notes = models.TextField( blank=True, verbose_name=_('Message to organizers'), help_text=_( 'If you have any constraint or if you have anything that may ' 'help you to select your talk, like a video or slides of your' ' talk, please write it down here. This field will only be ' 'visible by organizers.')) category = models.ForeignKey(TalkCategory, verbose_name=_('Talk Category'), on_delete=models.PROTECT) videotaped = models.BooleanField(_("I'm ok to be recorded on video"), default=True) video_licence = models.CharField(choices=LICENCES, default='CC-BY-SA', max_length=32, verbose_name=_("Video licence")) sound = models.BooleanField(_("I need sound"), default=False) accepted = models.BooleanField(null=True, default=None) confirmed = models.BooleanField(null=True, default=None) start_date = models.DateTimeField( null=True, blank=True, default=None, verbose_name=_('Beginning date and time')) duration = models.PositiveIntegerField(default=0, verbose_name=_('Duration (min)')) room = models.ForeignKey(Room, blank=True, null=True, default=None, on_delete=models.SET_NULL) plenary = models.BooleanField(default=False) materials = models.FileField( null=True, blank=True, upload_to=talks_materials_destination, verbose_name=_('Materials'), help_text= _('You can use this field to share some materials related to your intervention.' )) video = models.URLField(max_length=1000, blank=True, default='', verbose_name='Video URL') token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) conversation = models.OneToOneField(MessageThread, on_delete=models.PROTECT) objects = TalkManager() class Meta: ordering = ('title', ) def __str__(self): return self.title def get_speakers_str(self): speakers = list(map(str, self.speakers.all())) if len(speakers) == 0: return 'superman' elif len(speakers) == 1: return speakers[0] else: return ', '.join(speakers[:-1]) + ' & ' + str(speakers[-1]) def get_status_str(self): if self.accepted is True: if self.confirmed is True: return _('Confirmed') elif self.confirmed is False: return _('Cancelled') else: return _('Waiting confirmation') elif self.accepted is False: return _('Refused') else: return _('Pending decision, score: %(score).1f') % { 'score': self.score } def get_status_color(self): if self.accepted is True: if self.confirmed is True: return 'success' elif self.confirmed is False: return 'danger' else: return 'info' elif self.accepted is False: return 'default' else: return 'warning' def get_tags_html(self): return mark_safe(' '.join(map(lambda tag: tag.link, self.tags.all()))) def get_csv_row(self): return [ self.pk, self.title, self.description, self.category, self.track, [speaker.pk for speaker in self.speakers.all()], [speaker.name for speaker in self.speakers.all()], [tag.name for tag in self.tags.all()], 1 if self.videotaped else 0, self.video_licence, 1 if self.sound else 0, self.estimated_duration, self.room, 1 if self.plenary else 0, self.materials, self.video, ] @property def estimated_duration(self): return self.duration or self.category.duration def get_absolute_url(self): return reverse('talk-details', kwargs={'talk_id': self.pk}) @property def end_date(self): if self.estimated_duration: return self.start_date + timedelta(minutes=self.estimated_duration) else: return None @property def materials_name(self): return basename(self.materials.name) class Meta: ordering = ( 'category__id', 'title', )
class Collection(Resource, EntityUrlMixin): """Model representing an archival description.""" COLLECTION, FONDS, SUBFONDS, SERIES, SUBSERIES, FILE, ITEM = range(7) LODS = ( (COLLECTION, _("Collection")), (FONDS, _("Fonds")), (SUBFONDS, _("Sub-fonds")), (SERIES, _("Series")), (SUBSERIES, _("Sub-series")), (FILE, _("File")), (ITEM, _("Item")), ) translatable_fields = ( ("access_conditions", "Access Conditions", "TODO: Help text"), ("accruals", "Accruals", "TODO: Help text"), ("acquisition", "Immediate source of acquisition or transfer", "TODO: Help text"), ("alternate_title", "Alternate Title", "TODO: Help text"), ("appraisal", "Appraisal", "TODO: Help text"), ("archival_history", "Archival History", "TODO: Help text"), ("arrangement", "Arrangement", "TODO: Help text"), ("edition", "Edition", "TODO: Help text"), ("extent_and_medium", "Extent and Medium", "TODO: Help text"), ("finding_aids", "Finding Aids", "TODO: Help text"), ("institution_responsible_identifier", "Institution Responsible Identifier", "TODO: Help text"), ("location_of_copies", "Location of Copies", "TODO: Help text"), ("location_of_originals", "Location of Originals", "TODO: Help text"), ("notes", _("Notes"), "TODO: Help text"), ("physical_characteristics", "Physical Characteristics", "TODO: Help text"), ("related_units_of_description", "Related Units of Description", "TODO: Help text"), ("reproduction_conditions", "Reproduction Conditions", "TODO: Help text"), ("revision_history", "Revision History", "TODO: Help text"), ("rules", "Rules", "TODO: Help text"), ("scope_and_content", "Scope and Content", "TODO: Help text"), ("sources", "Sources", "TODO: Help text"), ) name = models.CharField("Title", max_length=255) slug = AutoSlugField(populate_from="name", unique=True) identifier = models.CharField(max_length=255) lod = models.PositiveIntegerField(_("Level of Description"), choices=LODS, blank=True, null=True) creator = models.ForeignKey("Authority", null=True, blank=True) repository = models.ForeignKey(Repository) languages = jsonfield.JSONField(_("Language(s) of Materials"), blank=True, default=EMPTY_JSON_LIST) scripts = jsonfield.JSONField(_("Script(s) of Materials"), blank=True, default=EMPTY_JSON_LIST) languages_of_description = jsonfield.JSONField( _("Language(s) of Description"), blank=True, default=EMPTY_JSON_LIST) scripts_of_description = jsonfield.JSONField(_("Script(s) of Description"), blank=True, default=EMPTY_JSON_LIST) tags = TaggableManager(blank=True) objects = CollectionManager() class Meta: verbose_name_plural = "collections" unique_together = (("identifier", "repository"), ) def natural_key(self): return (self.slug, ) @property def start_date(self): """Shortcut for getting the earliest date to which this collection relates.""" try: fdate = self.date_set.all().order_by("start_date")[0] except IndexError: return return fdate.start_date @property def end_date(self): """Shortcut for getting the lastest date to which this collection relates.""" try: edate = self.date_set.all().order_by("-end_date", "-start_date")[0] except IndexError: return if edate.end_date: return edate.end_date return edate.start_date @property def date(self): """Average of start/end dates. Not exact.""" if not self.end_date and not self.start_date: return if not self.end_date: return self.start_date return self.start_date + ((self.end_date - self.start_date) / 2) @property def date_range(self): """List of years this collection covers.""" if not self.end_date and not self.start_date: return [] if not self.end_date: return [self.start_date] return [datetime.date(y,1,1) for y in \ range(self.start_date.year, self.end_date.year + 1)] @property def date_range_string(self): """List of years this collection covers.""" dates = self.date_range if not dates: return None if len(dates) == 1: return str(self.start_date.year) return "%s-%s" % (dates[0].year, dates[-1].year) @property def name_access(self): # TODO: Find the proper way of doing this return [ne.subject for ne in NameAccess.objects.select_related()\ .filter(object_id=self.id).all()] def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.slug) def __unicode__(self): return self.name
class Link(models.Model): "A specific link within a topic." url = models.URLField() summary = models.CharField(max_length=255) slug = AutoSlugField(populate_from='summary', unique=True, max_length=255) text = models.TextField(u'Description') user = models.ForeignKey(User, related_name="added_links") topic = models.ForeignKey(Topic) created_on = models.DateTimeField(auto_now_add=1) liked_by = models.ManyToManyField(User, related_name="liked_links") disliked_by = models.ManyToManyField(User, related_name="disliked_links") liked_by_count = models.IntegerField(default=0) disliked_by_count = models.IntegerField(default=0) points = models.DecimalField(default=0, max_digits=7, decimal_places=2) recommended_done = models.BooleanField(default=False) # related_links_calculated = models.BooleanField(default=False) objects = LinkManager() """The Voting algo: On each upvote increase the points by min(voter.karma, 10) On each upvote decrease the points by min(voter.karma, 10) increase/decrease the voters karma by 1 """ def upvote(self, user): return self.vote(user, True) def downvote(self, user): return self.vote(user, False) def vote(self, user, direction=True): "Vote the given link either up or down, using a user. Calling multiple times with same user must have no effect." #Check if the current user can vote this, link or raise exception if self.topic.permissions == 'Public': pass #Anyone can vote else: try: subscribed_user = SubscribedUser.objects.get(topic=self.topic, user=user) except SubscribedUser.DoesNotExist: raise CanNotVote( 'The topic %s is non-public, and you are not subscribed to it.' % self.topic.name) vote, created, flipped = LinkVote.objects.do_vote(user=user, link=self, direction=direction) save_vote = False profile = user.get_profile() change = max(0, min(defaults.MAX_CHANGE_PER_VOTE, profile.karma)) if created and direction: self.liked_by_count += 1 self.points += change save_vote = True profile = self.user.get_profile() profile.karma += defaults.CREATORS_KARMA_PER_VOTE # print self.user, user, profile.karma if created and not direction: self.disliked_by_count += 1 self.points -= change save_vote = True profile = self.user.get_profile() profile.karma -= defaults.CREATORS_KARMA_PER_VOTE if direction and flipped: #Upvoted and Earlier downvoted self.liked_by_count += 1 self.disliked_by_count -= 1 self.points += 2 * change save_vote = True profile = self.user.get_profile() profile.karma += 2 * defaults.CREATORS_KARMA_PER_VOTE if not direction and flipped: #downvoted and Earlier upvoted self.liked_by_count -= 1 self.disliked_by_count += 1 self.points -= 2 * change save_vote = True profile = self.user.get_profile() profile.karma -= 2 * defaults.CREATORS_KARMA_PER_VOTE if not user == self.user: profile.save() if save_vote: self.save() return vote def reset_vote(self, user): "Reset a previously made vote" try: vote = LinkVote.objects.get(link=self, user=user) except LinkVote.DoesNotExist, e: "trying to reset vote, which does not exist." return change = max( 0, min(defaults.MAX_CHANGE_PER_VOTE, user.get_profile().karma)) if vote.direction: self.liked_by_count -= 1 self.points -= change self.save() profile = self.user.get_profile() profile.karma -= defaults.CREATORS_KARMA_PER_VOTE if not vote.direction: self.points += change self.disliked_by_count -= 1 self.save() profile = self.user.get_profile() profile.karma += defaults.CREATORS_KARMA_PER_VOTE if not user == self.user: profile.save() vote.delete() return vote
class Authority(Resource, EntityUrlMixin): """Model representing an archival authority.""" FULL, PARTIAL, MINIMAL = range(3) CORP, FAMILY, PERSON = range(3) LODS = ( (FULL, _("Full")), (PARTIAL, _("Partial")), (MINIMAL, _("Minimal")), ) ENTITY_TYPES = ( (CORP, _("Corporate Body")), (FAMILY, _("Family")), (PERSON, _("Person")), ) translatable_fields = ( ("dates_of_existence", "Dates of Existence", "TODO: Help text"), ("functions", "Functions", "TODO: Help text"), ("general_context", "General Context", "TODO: Help text"), ("history", "History", "TODO: Help text"), ("institution_responsible_identifier", "Institution Responsible Identifier", "TODO: Help text"), ("internal_structures", "Internal Structures", "TODO: Help text"), ("legal_status", "Legal Status", "TODO: Help text"), ("mandates", "Mandates", "TODO: Help text"), ("places", "Places", "TODO: Help text"), ("revision_history", "Revision History", "TODO: Help text"), ("sources", "Sources", "TODO: Help text"), ) name = models.CharField("Authorized Form of Name", max_length=255) slug = AutoSlugField(populate_from="name", unique=True) identifier = models.CharField(max_length=255) lod = models.PositiveIntegerField(_("Level of Description"), choices=LODS, blank=True, null=True) type_of_entity = models.PositiveIntegerField(_("Type of Entity"), choices=ENTITY_TYPES, blank=True, null=True) languages = jsonfield.JSONField(_("Language(s)"), blank=True, default=EMPTY_JSON_LIST) scripts = jsonfield.JSONField(_("Script(s)"), blank=True, default=EMPTY_JSON_LIST) tags = TaggableManager(blank=True) objects = AuthorityManager() class Meta: verbose_name_plural = "authorities" def natural_key(self): return (self.slug, ) @property def type_name(self): return self.ENTITY_TYPES[self.type_of_entity][1] def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.slug) def __unicode__(self): return self.name
class Category(mixins.Seo): name = models.CharField(_('name'), max_length=255) slug = AutoSlugField(_('slug'), populate_from='name', unique_with='parent', editable=True, blank=True) published = models.BooleanField(_('published'), default=True) order = models.PositiveSmallIntegerField(_('order'), default=0) language = LanguageField(_('language')) parent = models.ForeignKey('self', verbose_name=_('parent'), related_name='_subcategories', null=True, blank=True) description = models.TextField(_('description'), blank=True, default='') image = FileBrowseField(_('image'), max_length=255, null=True, blank=True) # category view - general show_description = models.BooleanField(_('show description'), default=True) show_image = models.BooleanField(_('show image'), default=True) image_size = FilebrowserVersionField(_('image size')) show_breadcrumbs = models.BooleanField(_('show bradcrumbs'), default=True) show_back_link = models.BooleanField(_('show back link'), default=True) # category view - subcategories show_subcategories_descriptions = models.BooleanField( _('show descriptions'), default=True) show_subcategories_images = models.BooleanField(_('show images'), default=True) subcategories_images_size = FilebrowserVersionField(_('images size')) # category view - articles show_articles_intros = models.BooleanField(_('show intros'), default=True) show_articles_contents = models.BooleanField(_('show contents'), default=False) show_articles_created = models.BooleanField(_('show creation dates'), default=False) show_articles_images = models.BooleanField(_('show images'), default=True) articles_images_size = FilebrowserVersionField(_('images size')) pagination = models.BooleanField(_('pagination'), default=True) articles_on_page = models.PositiveSmallIntegerField(_('articles on page'), default=10) # article view av_articles_images_size = FilebrowserVersionField(_('images size'), allow_null=True) class Meta: app_label = 'content' ordering = ('language', 'order', 'name') verbose_name = _('category') verbose_name_plural = _('categories') def __str__(self): return self.name @property def route(self): if not self.published: return None def try_parents(): tree = [] parent = self.parent while (parent): current = parent if not current.published: return None tree.append(current) parent = current.parent tree.reverse() tree.append(self) # root category must be published as page page = tree[0].pages.filter(published=True).first() if not page: return None if page.route == '/': path = '' else: path = page.route rest = '/'.join([c.slug for c in tree[1:]]) if rest: path += '/{}'.format(rest) return path def try_page(): page = self.pages.filter(published=True).first() if page: return page.route return try_parents() or try_page() @property def all_routes(self): if not self.published: return [] routes = [] if self.route: routes.append(self.route) for page in self.pages.filter(published=True): if page.route not in routes: routes.append(page.route) return routes @property def meta(self): return utils.generate_meta( title=self.meta_title_override or self.name, title_suffix=self.meta_title_site_name_suffix, description=self.meta_description_override, robots=self.meta_robots_override) @property def breadcrumbs(self): output = [{'name': self.name, 'route': self.route}] parent = self.parent while parent: output.append({'name': parent.name, 'route': parent.route}) parent = parent.parent return reversed(output) @property def back_link(self): try: return self.parent.route except AttributeError: return '/' @property def subcategories(self): return self._subcategories.filter(published=True, language__in=utils.served_langs()) @property def articles(self): return self._articles.filter(published=True, language__in=utils.served_langs()) def get_image(self, request): version = self.image_size try: return request.build_absolute_uri( self.image.original.version_generate(version).url) except (AttributeError, OSError): return None def get_subcategory_image(self, category, request): try: image = category.image.original except AttributeError: return None version = self.subcategories_images_size try: return request.build_absolute_uri( image.version_generate(version).url) except OSError: return None def get_article_image(self, article, request): try: image = article.thumbnail_or_first_image.original except AttributeError: return None version = self.articles_images_size try: return request.build_absolute_uri( image.version_generate(version).url) except OSError: return None
class Sermon(models.Model): title = models.CharField(max_length=120, help_text='The title of the sermon.') slug = AutoSlugField(populate_from='title', unique=True) date = models.DateField(help_text='The date the sermon was preached.') series = models.ForeignKey(SermonSeries, blank=True, null=True, help_text=('What series the sermon is from, if ' 'any - you can add a series using ' '<a href="/manage/sermons/series/' 'create/">this form</a>.')) speaker = models.ForeignKey(SermonSpeaker, help_text=('You can add a speaker using ' '<a href="/manage/sermons/speaker/' 'create/">this form</a>.')) passage = BiblePassageField(blank=True, null=True, help_text=('NB. This doesn\'t currently ' 'support multiple passages.')) mp3 = models.FileField(blank=True, null=True, upload_to='kanisa/sermons/mp3s/%Y/', max_length=200, verbose_name='MP3', help_text=('The MP3 will automatically have ID3 ' 'data filled in (e.g. title, genre).')) details = models.TextField(blank=True, null=True, help_text=('e.g. What themes does the sermon ' 'cover?')) transcript = models.TextField(blank=True, null=True, help_text=('For audio-impaired users - as ' 'close to a verbatim transcript ' 'as possible.')) downloads = models.IntegerField(default=0, editable=False) podcast_downloads = models.IntegerField(default=0, editable=False) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) objects = SermonManager() basic_objects = models.Manager() preached_objects = PreachedSermonManager() def __unicode__(self): return self.title def url(self): # I'm not sure this belongs here, but I don't really like # having to switch on whether or not a sermon is part of a # series when I need the URL for a sermon. I'll revisit this # later. if not self.series: return reverse('kanisa_public_standalone_sermon_detail', args=[ self.slug, ]) return reverse('kanisa_public_sermon_detail', args=[ self.series.slug, self.slug, ]) def mp3_url(self): if not self.mp3: return None return self.mp3.url def in_the_future(self): return self.date > date.today() def delete(self, using=None): if self.mp3: os.remove(self.mp3.file.name) return super(Sermon, self).delete(using) class Meta: # Need this because I've split up models.py into multiple # files. app_label = 'kanisa' ordering = ('-date', )
class Transient(BaseModel): ### Entity relationships ### # Required status = models.ForeignKey(TransientStatus, models.SET(get_sentinel_transientstatus)) obs_group = models.ForeignKey(ObservationGroup, on_delete=models.CASCADE) # Optional non_detect_band = models.ForeignKey(PhotometricBand, related_name='+', null=True, blank=True, on_delete=models.SET_NULL) best_spec_class = models.ForeignKey(TransientClass, related_name='+', null=True, blank=True, on_delete=models.SET_NULL) photo_class = models.ForeignKey(TransientClass, related_name='+', null=True, blank=True, on_delete=models.SET_NULL) best_spectrum = models.ForeignKey('TransientSpectrum', related_name='+', null=True, blank=True, on_delete=models.SET_NULL) host = models.ForeignKey(Host, null=True, blank=True, on_delete=models.SET_NULL) abs_mag_peak_band = models.ForeignKey(PhotometricBand, related_name='+', null=True, blank=True, on_delete=models.SET_NULL) antares_classification = models.ForeignKey(AntaresClassification, null=True, blank=True, on_delete=models.SET_NULL) internal_survey = models.ForeignKey(InternalSurvey, null=True, blank=True, on_delete=models.SET_NULL) tags = models.ManyToManyField(TransientTag, blank=True) ### Properties ### # Required name = models.CharField(max_length=64) ra = models.FloatField() dec = models.FloatField() # Optional disc_date = models.DateTimeField(null=True, blank=True) candidate_hosts = models.TextField( null=True, blank=True ) # A string field to hold n hosts -- if we don't quite know which is the correct one redshift = models.FloatField(null=True, blank=True) redshift_err = models.FloatField(null=True, blank=True) redshift_source = models.CharField(max_length=64, null=True, blank=True) non_detect_date = models.DateTimeField(null=True, blank=True) non_detect_limit = models.FloatField(null=True, blank=True) mw_ebv = models.FloatField(null=True, blank=True) abs_mag_peak = models.FloatField(null=True, blank=True) abs_mag_peak_date = models.DateTimeField(null=True, blank=True) postage_stamp_file = models.CharField(max_length=512, null=True, blank=True) k2_validated = models.NullBooleanField(null=True, blank=True) k2_msg = models.TextField(null=True, blank=True) TNS_spec_class = models.CharField( max_length=64, null=True, blank=True ) # To hold the TNS classiciation in case we don't have a matching enum point_source_probability = models.FloatField(null=True, blank=True) slug = AutoSlugField(null=True, default=None, unique=True, populate_from='name') def CoordString(self): return GetSexigesimalString(self.ra, self.dec) def RADecimalString(self): return '%.7f' % (self.ra) def DecDecimalString(self): return '%.7f' % (self.dec) def Separation(self): host = Host.objects.get(pk=self.host_id) return '%.2f' % getSeparation(self.ra, self.dec, host.ra, host.dec) def modified_date_pacific(self): date_format = '%m/%d/%Y %H:%M:%S %Z' mod_date = self.modified_date.astimezone(timezone('US/Pacific')) return mod_date.strftime(date_format) def disc_date_string(self): date_format = '%m/%d/%Y' return self.disc_date.strftime(date_format) def disc_mag(self): transient_query = Q(transient=self.id) all_phot = yse_models.TransientPhotometry.objects.filter( transient_query) phot_ids = all_phot.values('id') phot_data_query = Q(photometry__id__in=phot_ids) disc_query = Q(discovery_point=1) disc_mag = yse_models.TransientPhotData.objects.exclude( data_quality__isnull=False).filter(phot_data_query & disc_query) if len(disc_mag): return disc_mag[0].mag else: return None def recent_mag(self): date_format = '%m/%d/%Y' transient_query = Q(transient=self.id) all_phot = yse_models.TransientPhotometry.objects.filter( transient_query) phot_ids = all_phot.values('id') phot_data_query = Q(photometry__id__in=phot_ids) recent_mag = yse_models.TransientPhotData.objects.exclude( data_quality__isnull=False).filter(phot_data_query).order_by( '-obs_date') if len(recent_mag): return '%.2f' % (recent_mag[0].mag) else: return None def recent_magdate(self): date_format = '%m/%d/%Y' transient_query = Q(transient=self.id) all_phot = yse_models.TransientPhotometry.objects.filter( transient_query) phot_ids = all_phot.values('id') phot_data_query = Q(photometry__id__in=phot_ids) recent_mag = yse_models.TransientPhotData.objects.exclude( data_quality__isnull=False).filter(phot_data_query).order_by( '-obs_date') if len(recent_mag): return '%s' % (recent_mag[0].obs_date.strftime(date_format)) else: return None def z_or_hostz(self): if self.redshift: return self.redshift elif self.host and self.host.redshift: return self.host.redshift else: return None def name_table_sort(self): if len(self.name) > 4: addnums = 7 - len(self.name) sortname = "".join( [self.name[:4], "".join(['1'] * addnums), self.name[4:]]) return sortname else: return None def __str__(self): return self.name def natural_key(self): return self.name
class SermonSeries(models.Model): title = models.CharField(max_length=120, help_text='The name of the series.') slug = AutoSlugField(populate_from='title', unique=True) image = ImageField( null=True, blank=True, upload_to='kanisa/sermons/series/', help_text=('This will be used in most places where the series is ' 'shown on the site. Must be at least 400px by 300px.')) intro = models.TextField( blank=True, null=True, help_text=('Sum up this series in a few sentences. In some places ' 'this may be displayed without the details section ' 'below.')) details = models.TextField( blank=True, null=True, help_text=('e.g. What themes does the series cover?')) active = models.BooleanField(default=True, help_text='Is this series currently ongoing?') passage = BiblePassageField( blank=True, null=True, help_text=('NB. This doesn\'t currently support multiple passages.')) modified = models.DateTimeField(auto_now=True) objects = SermonSeriesManager() def __unicode__(self): return self.title def num_sermons(self): return self.the_num_sermons def sermons(self): return self.sermon_set.order_by('date').all() def date_range(self): """Returns a (first_date, last_date) tuple representing the date of the first sermon and the date of the last sermon. If the series is currently active (implying the last sermon has not yet been added to the series), the second element in the tuple will be None. If the series has no sermons, None will be returned. """ sermons = Sermon.basic_objects.filter(series=self) sermons = list(sermons.order_by('date').only('date')) if not sermons: return None first_sermon = sermons[0] if self.active: return (first_sermon.date, None) last_sermon = sermons[-1] return (first_sermon.date, last_sermon.date) def image_or_default(self): if self.image: return self.image branding = BrandingInformation('apple') return branding.url class Meta: # Need this because I've split up models.py into multiple # files. app_label = 'kanisa' ordering = ('-active', ) verbose_name_plural = 'Sermon series' permissions = (('manage_sermons', 'Can manage your sermons'), )
class Ad(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) author = models.ForeignKey('auth.User', on_delete=models.CASCADE, default='rohanjha') product_name = models.CharField(max_length=260) slug = AutoSlugField(populate_from='product_name') price = models.DecimalField(max_digits=15, decimal_places=2, default=0.0) discount_amount = models.IntegerField(blank=True, null=True, default=0) description = models.TextField() product_brand = models.CharField(max_length=250, default='None') sizes_available = models.CharField(max_length=200, default='XL L M') stock = models.IntegerField(max_length=100, default=1, blank=True, null=True) product_pic0 = models.ImageField(blank=True, null=True, upload_to='images/%Y/%m/%d/') product_pic1 = models.ImageField(blank=True, null=True, upload_to='images/%Y/%m/%d/') product_pic2 = models.ImageField(blank=True, null=True, upload_to='images/%Y/%m/%d/') product_pic3 = models.ImageField(blank=True, null=True, upload_to='images/%Y/%m/%d/') featured = models.BooleanField(default=False) active = models.BooleanField(default=True) category = models.ForeignKey('ShopApp.Category', related_name='advertisements', on_delete=models.CASCADE) subcategory = models.ForeignKey('ShopApp.SubCategory', related_name='advertisements', on_delete=models.CASCADE) published_date = models.DateTimeField(default=timezone.now) def __str__(self): return self.product_name def get_absolute_url(self): return reverse("ad_detail", kwargs={"pk": self.pk}) def save(self): if (self.product_pic0): self.product_pic0 = self.ModifyImage(self.product_pic0) if (self.product_pic1): self.product_pic1 = self.ModifyImage(self.product_pic1) if (self.product_pic2): self.product_pic2 = self.ModifyImage(self.product_pic2) if (self.product_pic3): self.product_pic3 = self.ModifyImage(self.product_pic3) super().save() def ModifyImage(self, product_pic): im = Image.open(product_pic) output = BytesIO() im = im.resize((600, 380), PIL.Image.ANTIALIAS) im.save(output, format='JPEG', quality=80) output.seek(0) product_pic = InMemoryUploadedFile( output, 'ImageField', "%s.jpg" % product_pic.name.split('.')[0], 'image/jpeg', sys.getsizeof(output), None) return product_pic
class RAAppointment(models.Model): """ This stores information about a (Research Assistant)s application and pay. """ person = models.ForeignKey(Person, help_text='The RA who is being appointed.', null=False, blank=False, related_name='ra_person', on_delete=models.PROTECT) sin = models.PositiveIntegerField(null=True, blank=True) # We want do add some sort of accountability for checking visas. visa_verified = models.BooleanField(default=False, help_text='I have verified this RA\'s visa information') hiring_faculty = models.ForeignKey(Person, help_text='The manager who is hiring the RA.', related_name='ra_hiring_faculty', on_delete=models.PROTECT) unit = models.ForeignKey(Unit, help_text='The unit that owns the appointment', null=False, blank=False, on_delete=models.PROTECT) hiring_category = models.CharField(max_length=4, choices=HIRING_CATEGORY_CHOICES, default='GRA') scholarship = models.ForeignKey(Scholarship, null=True, blank=True, help_text='Scholarship associated with this appointment. Optional.', on_delete=models.PROTECT) project = models.ForeignKey(Project, null=False, blank=False, on_delete=models.PROTECT) account = models.ForeignKey(Account, null=False, blank=False, help_text='This is now called "Object" in the new PAF', on_delete=models.PROTECT) program = models.ForeignKey(Program, null=True, blank=True, help_text='If none is provided, "00000" will be added in the PAF', on_delete=models.PROTECT) start_date = models.DateField(auto_now=False, auto_now_add=False) end_date = models.DateField(auto_now=False, auto_now_add=False) pay_frequency = models.CharField(max_length=60, choices=PAY_FREQUENCY_CHOICES, default='B') lump_sum_pay = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Total Pay") lump_sum_hours = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Total Hours", blank=True, null=True) biweekly_pay = models.DecimalField(max_digits=8, decimal_places=2) pay_periods = models.DecimalField(max_digits=6, decimal_places=1) hourly_pay = models.DecimalField(max_digits=8, decimal_places=2) hours = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Biweekly Hours") reappointment = models.BooleanField(default=False, help_text="Are we re-appointing to the same position?") medical_benefits = models.BooleanField(default=False, help_text="50% of Medical Service Plan") dental_benefits = models.BooleanField(default=False, help_text="50% of Dental Plan") # The two following fields verbose names are reversed for a reason. They were named incorrectly with regards to # the PAF we generate, so the verbose names are correct. notes = models.TextField("Comments", blank=True, help_text="Biweekly employment earnings rates must include vacation pay, hourly rates will automatically have vacation pay added. The employer cost of statutory benefits will be charged to the amount to the earnings rate.") comments = models.TextField("Notes", blank=True, help_text="For internal use") offer_letter_text = models.TextField(null=True, help_text="Text of the offer letter to be signed by the RA and supervisor.") def autoslug(self): if self.person.userid: ident = self.person.userid else: ident = str(self.person.emplid) return make_slug(self.unit.label + '-' + str(self.start_date.year) + '-' + ident) slug = AutoSlugField(populate_from='autoslug', null=False, editable=False, unique=True) created_at = models.DateTimeField(auto_now_add=True) deleted = models.BooleanField(null=False, default=False) config = JSONField(null=False, blank=False, default=dict) # addition configuration stuff defaults = {'use_hourly': False} use_hourly, set_use_hourly = getter_setter('use_hourly') def __str__(self): return str(self.person) + "@" + str(self.created_at) class Meta: ordering = ['person', 'created_at'] def save(self, *args, **kwargs): # set SIN field on the Person object if self.sin and 'sin' not in self.person.config: self.person.set_sin(self.sin) self.person.save() super(RAAppointment, self).save(*args, **kwargs) def get_absolute_url(self): return reverse('ra:view', kwargs={'ra_slug': self.slug}) def mark_reminded(self): self.config['reminded'] = True self.save() @staticmethod def letter_choices(units): """ Return a form choices list for RA letter templates in these units. Ignores the units for now: we want to allow configurability later. """ return [(key, label) for (key, (label, _, _)) in list(DEFAULT_LETTERS.items())] def build_letter_text(self, selection): """ This takes the value passed from the letter selector menu and builds the appropriate default letter based on that. """ substitutions = { 'start_date': self.start_date.strftime("%B %d, %Y"), 'end_date': self.end_date.strftime("%B %d, %Y"), 'lump_sum_pay': self.lump_sum_pay, 'biweekly_pay': self.biweekly_pay, } _, lumpsum_text, biweekly_text = DEFAULT_LETTERS[selection] if self.pay_frequency == 'B': text = biweekly_text else: text = lumpsum_text letter_text = text % substitutions self.offer_letter_text = letter_text self.save() def letter_paragraphs(self): """ Return list of paragraphs in the letter (for PDF creation) """ text = self.offer_letter_text text = normalize_newlines(text) return text.split("\n\n") @classmethod def semester_guess(cls, date): """ Guess the semester for a date, in the way that financial people do (without regard to class start/end dates) """ mo = date.month if mo <= 4: se = 1 elif mo <= 8: se = 4 else: se = 7 semname = str((date.year-1900)*10 + se) return Semester.objects.get(name=semname) @classmethod def start_end_dates(cls, semester): """ First and last days of the semester, in the way that financial people do (without regard to class start/end dates) """ return Semester.start_end_dates(semester) #yr = int(semester.name[0:3]) + 1900 #sm = int(semester.name[3]) #if sm == 1: # start = datetime.date(yr, 1, 1) # end = datetime.date(yr, 4, 30) #elif sm == 4: # start = datetime.date(yr, 5, 1) # end = datetime.date(yr, 8, 31) #elif sm == 7: # start = datetime.date(yr, 9, 1) # end = datetime.date(yr, 12, 31) #return start, end def start_semester(self): "Guess the starting semester of this appointment" start_semester = RAAppointment.semester_guess(self.start_date) # We do this to eliminate hang - if you're starting N days before # semester 1134, you aren't splitting that payment across 2 semesters. start, end = RAAppointment.start_end_dates(start_semester) if end - self.start_date < datetime.timedelta(SEMESTER_SLIDE): return start_semester.next_semester() return start_semester def end_semester(self): "Guess the ending semester of this appointment" end_semester = RAAppointment.semester_guess(self.end_date) # We do this to eliminate hang - if you're starting N days after # semester 1134, you aren't splitting that payment across 2 semesters. start, end = RAAppointment.start_end_dates(end_semester) if self.end_date - start < datetime.timedelta(SEMESTER_SLIDE): return end_semester.previous_semester() return end_semester def semester_length(self): "The number of semesters this contracts lasts for" return self.end_semester() - self.start_semester() + 1 @classmethod def expiring_appointments(cls): """ Get the list of RA Appointments that will expire in the next few weeks so we can send a reminder email """ today = datetime.datetime.now() min_age = datetime.datetime.now() + datetime.timedelta(days=14) expiring_ras = RAAppointment.objects.filter(end_date__gt=today, end_date__lte=min_age, deleted=False) ras = [ra for ra in expiring_ras if 'reminded' not in ra.config or not ra.config['reminded']] return ras @classmethod def email_expiring_ras(cls): """ Emails the supervisors of the RAs who have appointments that are about to expire. """ subject = 'RA appointment expiry reminder' from_email = settings.DEFAULT_FROM_EMAIL expiring_ras = cls.expiring_appointments() template = get_template('ra/emails/reminder.txt') for raappt in expiring_ras: supervisor = raappt.hiring_faculty context = {'supervisor': supervisor, 'raappt': raappt} # Let's see if we have any Funding CC supervisors that should also get the reminder. cc = None fund_cc_roles = Role.objects_fresh.filter(unit=raappt.unit, role='FDCC') # If we do, let's add them to the CC list, but let's also make sure to use their role account email for # the given role type if it exists. if fund_cc_roles: people = [] for role in fund_cc_roles: people.append(role.person) people = list(set(people)) cc = [] for person in people: cc.append(person.role_account_email('FDCC')) msg = EmailMultiAlternatives(subject, template.render(context), from_email, [supervisor.email()], headers={'X-coursys-topic': 'ra'}, cc=cc) msg.send() raappt.mark_reminded() def get_program_display(self): if self.program: return self.program.get_program_number_display() else: return '00000' def has_attachments(self): return self.attachments.visible().count() > 0
class FormWizardEntry(models.Model): """Form wizard entry.""" user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE) name = models.CharField(_("Name"), max_length=255) title = models.CharField(_("Title"), max_length=255, null=True, blank=True, help_text=_("Shown in templates if available.")) slug = AutoSlugField(populate_from='name', verbose_name=_("Slug"), unique=True) is_public = models.BooleanField( _("Is public?"), default=False, help_text=_("Makes your form wizard visible to the public.")) is_cloneable = models.BooleanField( _("Is cloneable?"), default=False, help_text=_("Makes your form wizard cloneable by other users.")) success_page_title = models.CharField( _("Success page title"), max_length=255, null=True, blank=True, help_text=_("Custom message title to display after valid form is " "submitted")) success_page_message = models.TextField( _("Success page body"), null=True, blank=True, help_text=_("Custom message text to display after valid form is " "submitted")) show_all_navigation_buttons = models.BooleanField( _("Show all navigation buttons?"), default=False, help_text=_("Show all navigation buttons.")) # action = models.CharField( # _("Action"), max_length=255, null=True, blank=True, # help_text=_("Custom form action; don't fill this field, unless " # "really necessary.") # ) wizard_type = models.CharField(_("Type"), max_length=255, null=False, blank=False, choices=WIZARD_TYPES, default=DEFAULT_WIZARD_TYPE, help_text=_("Type of the form wizard.")) created = models.DateTimeField(_("Created"), null=True, blank=True, auto_now_add=True) updated = models.DateTimeField(_("Updated"), null=True, blank=True, auto_now=True) class Meta(object): """Meta class.""" verbose_name = _("Form wizard entry") verbose_name_plural = _("Form wizard entries") unique_together = ( ('user', 'slug'), ('user', 'name'), ) def __str__(self): return self.name def get_absolute_url(self): """Get absolute URL. Absolute URL, which goes to the form-wizard view view. :return string: """ return reverse('fobi.view_form_wizard_entry', kwargs={'form_wizard_entry_slug': self.slug})
class ProductModelBase(CreateUpdateModelBase): ''' This is the base class that all Products should inherit from. ''' sku = models.CharField( _("SKU"), max_length=40, unique=True, blank=True, null=True, help_text=_("User Defineable SKU field") ) # Needs to be autogenerated by default, and unique from the PK uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) # Used to track the product name = models.CharField(_("Name"), max_length=80, blank=False) site = models.ForeignKey(Site, verbose_name=_("Site"), on_delete=models.CASCADE, default=settings.SITE_ID, related_name="products") # For multi-site support slug = AutoSlugField(populate_from='name', unique_with='site__id') # Gets set in the save available = models.BooleanField( _("Available"), default=False, help_text=_("Is this currently available?") ) # This can be forced to be unavailable if there is no prices attached. description = models.JSONField( _("Description"), default=product_description_default, blank=True, null=True, help_text=_("Eg: {'call out': 'The ultimate product'}")) meta = models.JSONField( _("Meta"), validators=[validate_msrp], default=product_meta_default, blank=True, null=True, help_text=_( "Eg: { 'msrp':{'usd':10.99} }\n(iso4217 Country Code):(MSRP Price)" )) classification = models.ManyToManyField( "vendor.TaxClassifier", blank=True) # What taxes can apply to this item offers = models.ManyToManyField("vendor.Offer", blank=True, related_name="products") receipts = models.ManyToManyField("vendor.Receipt", blank=True, related_name="products") objects = models.Manager() on_site = CurrentSiteManager() class Meta: abstract = True def __str__(self): return self.name def get_msrp(self, currency): if currency in self.meta['msrp']: return self.meta['msrp'][currency] else: return self.meta['msrp'][self.meta['msrp']['default']] def add_to_cart_url(self): """ Link to add the item to the user's cart. """ # TODO: ADD trigger when object becomes unavailable to disable offer if it exisits. def get_best_currency(self, currency=DEFAULT_CURRENCY): """ If no currency is provided as an argument it will default to the products's msrp default currency. If currency is provided but is not available in the product it will default to the products's msrp default currency. """ if is_currency_available(self.meta['msrp'].keys(), currency=currency): return currency else: return self.meta['msrp']['default']
class DataRequestProject(models.Model): """ Base class for data request projects. Some fields are only available to Open Humans admins, including: all_sources_access (Boolean): when True, all data sources shared w/proj approved (Boolean): when True, member cap is removed and proj is listed token_expiration_disabled (Boolean): if True master tokens don't expire """ BOOL_CHOICES = ((True, "Yes"), (False, "No")) STUDY_CHOICES = ((True, "Study"), (False, "Activity")) is_study = models.BooleanField( choices=STUDY_CHOICES, help_text=('A "study" is doing human subjects research and must have ' "Institutional Review Board approval or equivalent ethics " "board oversight. Activities can be anything else, e.g. " "data visualizations."), verbose_name="Is this project a study or an activity?", ) name = models.CharField(max_length=100, verbose_name="Project name") slug = AutoSlugField(populate_from="name", unique=True, always_update=True) leader = models.CharField( max_length=100, verbose_name="Leader(s) or principal investigator(s)") organization = models.CharField(blank=True, max_length=100, verbose_name="Organization or institution") is_academic_or_nonprofit = models.BooleanField( choices=BOOL_CHOICES, verbose_name=("Is this institution or organization an academic " "institution or non-profit organization?"), ) add_data = models.BooleanField( help_text=('If your project collects data, choose "Add data" here. If ' 'you choose "Add data", you will need to provide a ' '"Returned data description" below.'), verbose_name="Add data", default=False, ) explore_share = models.BooleanField( help_text=("If your project performs analysis on data, choose " '"Explore & share".'), verbose_name="Explore & share", default=False, ) contact_email = models.EmailField( verbose_name="Contact email for your project") info_url = models.URLField( blank=True, verbose_name="URL for general information about your project") short_description = models.CharField( max_length=140, verbose_name="A short description (140 characters max)") long_description = models.TextField( max_length=1000, verbose_name="A long description (1000 characters max)") returned_data_description = models.CharField( blank=True, max_length=140, verbose_name=("Description of data you plan to upload to member " " accounts (140 characters max)"), help_text=("Leave this blank if your project doesn't plan to add or " "return new data for your members. If your project is set " 'to be displayed under "Add data", then you must provide ' "this information."), ) active = models.BooleanField(choices=BOOL_CHOICES, help_text=active_help_text, default=True) badge_image = models.ImageField( blank=True, storage=PublicStorage(), upload_to=badge_upload_path, max_length=1024, help_text=("A badge that will be displayed on the user's profile once " "they've connected your project."), ) requested_sources = models.ManyToManyField( "self", related_name="requesting_projects", symmetrical=False, blank=True) all_sources_access = models.BooleanField(default=False) deauth_email_notification = models.BooleanField( default=False, help_text="Receive emails when a member deauthorizes your project", verbose_name="Deauthorize email notifications", ) erasure_supported = models.BooleanField( default=False, help_text= "Whether your project supports erasing a member's data on request", ) request_username_access = models.BooleanField( choices=BOOL_CHOICES, help_text=("Access to the member's username. This implicitly enables " "access to anything the user is publicly sharing on Open " "Humans. Note that this is potentially sensitive and/or " "identifying."), verbose_name="Are you requesting Open Humans usernames?", ) registered_datatypes = models.ManyToManyField(DataType, blank=True) class Meta: ordering = ["name"] coordinator = models.ForeignKey(Member, on_delete=models.PROTECT) approved = models.BooleanField(default=False) approval_history = ArrayField( ArrayField(models.CharField(max_length=32), size=2), default=list, editable=False, ) created = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) master_access_token = models.CharField(max_length=64, default=generate_id) token_expiration_date = models.DateTimeField(default=now_plus_24_hours) token_expiration_disabled = models.BooleanField(default=False) no_public_data = models.BooleanField(default=False) auto_add_datatypes = models.BooleanField(default=False) def __init__(self, *args, **kwargs): # Adds self.old_approved so that we can detect when the field changes super().__init__(*args, **kwargs) self.old_approved = self.approved def __str__(self): return str("{0}").format(self.name) def save(self, *args, **kwargs): """ Override save to update the timestamp for when approved gets changed. """ if self.old_approved != self.approved: self.approval_history.append( (self.approved, datetime.datetime.utcnow().isoformat())) return super().save(*args, **kwargs) @property def project_approval_date(self): """ Returns None if project is not approved, most recent approval date otherwise. """ if not self.approved: return None if self.approval_history == []: return None return dateutil.parser.parse(self.approval_history[-1][1]) def refresh_token(self): """ Generate a new master access token that expires in 24 hours. """ self.master_access_token = generate_id() self.token_expiration_date = now_plus_24_hours() self.save() @property def id_label(self): return str("direct-sharing-{0}").format(self.id) @property def project_type(self): return "study" if self.is_study else "activity" @property def type(self): if hasattr(self, "oauth2datarequestproject"): return "oauth2" if hasattr(self, "onsitedatarequestproject"): return "on-site" @property def authorized_members(self): return self.project_members.filter_active().count() def active_user(self, user): try: return DataRequestProjectMember.objects.get( member__user=user, project=self, joined=True, authorized=True, revoked=False, ) except (TypeError, AttributeError, DataRequestProjectMember.DoesNotExist): return None def is_joined(self, user): if self.active_user(user): return True else: return False @property def join_url(self): if self.type == "on-site": return reverse("direct-sharing:join-on-site", kwargs={"slug": self.slug}) return self.oauth2datarequestproject.enrollment_url @property def connect_verb(self): return "join" if self.type == "on-site" else "connect" def delete_without_cascade(self, using=None, keep_parents=False): """ Modified version of django's default delete() method. This method is added to enable safe deletion of the child models without removing objects related to it through the parent. As of Feb 2017, no models are directly related to the OAuth2DataRequestProject or OnSiteDataRequestProject child models. """ allowed_models = [ "private_sharing.onsitedatarequestproject", "private_sharing.oauth2datarequestproject", ] if self._meta.label_lower not in allowed_models: raise Exception("'delete_without_cascade' only for child models!") using = using or router.db_for_write(self.__class__, instance=self) assert self._get_pk_val() is not None, ( "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)) collector = Collector(using=using) collector.collect([self], keep_parents=keep_parents, collect_related=False) return collector.delete()
class Language(models.Model): title = models.CharField(max_length=100) title_slug = AutoSlugField(unique=True, populate_from='title') def __str__(self): return self.title