class ApplicationBase(EmailForm, WorkflowStreamForm): # type: ignore is_createable = False # Adds validation around forms & workflows. Isn't on Workflow class due to not displaying workflow field on Round base_form_class = WorkflowFormAdminForm reviewers = ParentalManyToManyField( settings.AUTH_USER_MODEL, related_name='%(class)s_reviewers', limit_choices_to=LIMIT_TO_REVIEWERS, blank=True, ) slack_channel = models.CharField(blank=True, max_length=128, help_text=_('The slack #channel for notifications.')) objects = PageManager.from_queryset(ApplicationBaseManager)() parent_page_types = ['apply_home.ApplyHomePage'] def get_template(self, request, *args, **kwargs): # We want to force children to use our base template # template attribute is ignored by children return 'funds/application_base.html' def detail(self): # The location to find out more information return self.application_public.first() @cached_property def open_round(self): return RoundBase.objects.child_of(self).open().first() def next_deadline(self): try: return self.open_round.end_date except AttributeError: # There isn't an open round return None def serve(self, request): if hasattr(request, 'is_preview') or not self.open_round: return super().serve(request) # delegate to the open_round to use the latest form instances request.show_round = True return self.open_round.serve(request) content_panels = WorkflowStreamForm.content_panels + [ FieldPanel('reviewers'), FieldPanel('slack_channel'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), EmailForm.email_tab, ObjectList(WorkflowStreamForm.promote_panels, heading='Promote'), ])
class PersonPage(FundingMixin, BasePage): subpage_types = [] parent_page_types = ['PersonIndexPage'] drupal_id = models.IntegerField(null=True, blank=True, editable=False) first_name = models.CharField(max_length=255, blank=True) last_name = models.CharField(max_length=255) photo = models.ForeignKey( 'images.CustomImage', null=True, blank=True, related_name='+', on_delete=models.SET_NULL ) active = models.BooleanField(default=True) job_title = models.CharField(max_length=255, blank=True) introduction = models.TextField(blank=True) website = models.URLField(blank=True, max_length=255) biography = StreamField(StoryBlock(), blank=True) email = models.EmailField(blank=True) objects = PageManager.from_queryset(PersonQuerySet)() search_fields = BasePage.search_fields + [ index.SearchField('introduction'), index.SearchField('biography') ] content_panels = BasePage.content_panels + [ MultiFieldPanel([ FieldPanel('first_name'), FieldPanel('last_name'), ], heading="Name"), FieldPanel('active'), ImageChooserPanel('photo'), FieldPanel('job_title'), InlinePanel('social_media_profile', label='Social accounts'), FieldPanel('website'), MultiFieldPanel([ FieldPanel('email'), InlinePanel('contact_details', label='Other Contact Methods'), ], heading='Contact information'), InlinePanel('person_types', label='Person types'), FieldPanel('introduction'), StreamFieldPanel('biography'), InlinePanel('funds_reviewed', label='Funds Reviewed'), ] + FundingMixin.content_panels
class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore is_creatable = False submission_class = ApplicationSubmission objects = PageManager.from_queryset(RoundBaseManager)() subpage_types = [] # type: ignore # Adds validation for making start_date required base_form_class = RoundBasePageAdminForm lead = models.ForeignKey( settings.AUTH_USER_MODEL, limit_choices_to=LIMIT_TO_STAFF, related_name='%(class)s_lead', on_delete=models.PROTECT, ) reviewers = ParentalManyToManyField( settings.AUTH_USER_MODEL, related_name='%(class)s_reviewer', limit_choices_to=LIMIT_TO_REVIEWERS, blank=True, ) start_date = models.DateField(null=True, blank=True, default=date.today) end_date = models.DateField( blank=True, null=True, default=date.today, help_text='When no end date is provided the round will remain open indefinitely.' ) sealed = models.BooleanField(default=False) content_panels = SubmittableStreamForm.content_panels + [ FieldPanel('lead'), MultiFieldPanel([ FieldRowPanel([ FieldPanel('start_date'), FieldPanel('end_date'), ]), ], heading="Dates"), FieldPanel('reviewers', widget=forms.SelectMultiple(attrs={'size': '16'})), ReadOnlyPanel('get_workflow_name_display', heading="Workflow", help_text="Copied from the fund."), # Forms comes from parental key in models/forms.py ReadOnlyInlinePanel('forms', help_text="Copied from the fund."), ReadOnlyInlinePanel('review_forms', help_text="Copied from the fund."), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(SubmittableStreamForm.promote_panels, heading='Promote'), ]) def get_template(self, request, *args, **kwargs): # Make sure all children use the shared template return 'funds/round.html' def get_landing_page_template(self, request, *args, **kwargs): # Make sure all children use the shared template return 'funds/round_landing.html' @cached_property def fund(self): return self.get_parent() @property def is_sealed(self): return self.sealed and self.is_open @property def is_open(self): return self.start_date <= date.today() <= self.end_date def save(self, *args, **kwargs): is_new = not self.id if is_new and hasattr(self, 'parent_page'): parent_page = self.parent_page[self.__class__][self.title] self.workflow_name = parent_page.workflow_name self.reviewers = parent_page.reviewers.all() super().save(*args, **kwargs) if is_new and hasattr(self, 'parent_page'): # Would be nice to do this using model clusters as part of the __init__ self._copy_forms('forms') self._copy_forms('review_forms') def _copy_forms(self, field): for form in getattr(self.get_parent().specific, field).all(): new_form = self._meta.get_field(field).related_model self._copy_form(form, new_form) def _copy_form(self, form, new_class): # Create a copy of the existing form object new_form = form.form new_form.id = None new_form.name = '{} for {} ({})'.format(new_form.name, self.title, self.get_parent().title) new_form.save() if hasattr(form, 'stage'): new_class.objects.create(round=self, form=new_form, stage=form.stage) else: new_class.objects.create(round=self, form=new_form) def get_submit_meta_data(self, **kwargs): return super().get_submit_meta_data( page=self.get_parent(), round=self, **kwargs, ) def clean(self): super().clean() conflict_query = () if self.start_date and self.end_date and self.start_date > self.end_date: raise ValidationError({ 'end_date': 'End date must come after the start date', }) if self.start_date and self.end_date: conflict_query = ( Q(start_date__range=[self.start_date, self.end_date]) | Q(end_date__range=[self.start_date, self.end_date]) | Q(start_date__lte=self.start_date, end_date__gte=self.end_date) ) elif self.start_date: conflict_query = ( Q(start_date__lte=self.start_date, end_date__isnull=True) | Q(end_date__gte=self.start_date) ) if not self.id and hasattr(self, 'parent_page'): # Check if the create hook has added the parent page, we aren't an object yet. # Ensures we can access related objects during the clean phase instead of save. base_query = RoundBase.objects.child_of(self.parent_page[self.__class__][self.title]) else: # don't need parent page, we are an actual object now. base_query = RoundBase.objects.sibling_of(self) if conflict_query: conflicting_rounds = base_query.filter( conflict_query ).exclude(id=self.id) if conflicting_rounds.exists(): error_message = mark_safe('Overlaps with the following rounds:<br> {}'.format( '<br>'.join([ f'<a href="{admin_url(round)}">{round.title}</a>: {round.start_date} - {round.end_date}' for round in conflicting_rounds] ) )) error = { 'start_date': error_message, } if self.end_date: error['end_date'] = error_message raise ValidationError(error) def get_initial_data_open_call_submission(self, submission_id): initial_values = {} try: submission_class = self.get_submission_class() submission = submission_class.objects.get(id=submission_id) if submission.status in OPEN_CALL_PHASES and self.get_parent() == submission.page: title_block_id = submission.named_blocks.get('title') if title_block_id: field_data = submission.data(title_block_id) initial_values[title_block_id] = field_data + ' (please edit)' for field_id in submission.first_group_normal_text_blocks: field_data = submission.data(field_id) initial_values[field_id] = field_data # Select first item in the Group toggle blocks for toggle_block_id, toggle_field in submission.group_toggle_blocks: try: initial_values[toggle_block_id] = toggle_field.value['choices'][0] except IndexError: initial_values[toggle_block_id] = 'yes' except KeyError: pass except (submission_class.DoesNotExist, ValueError): pass return initial_values def get_form_parameters(self, submission_id=None): form_parameters = {} if submission_id: initial_values = self.get_initial_data_open_call_submission(submission_id) if initial_values: form_parameters['initial'] = initial_values return form_parameters def get_form(self, *args, **kwargs): draft = kwargs.pop('draft', False) form_class = self.get_form_class(draft) submission_id = kwargs.pop('submission_id', None) if submission_id: form_params = self.get_form_parameters(submission_id=submission_id) else: form_params = self.get_form_parameters() form_params.update(kwargs) return form_class(*args, **form_params) def serve(self, request, *args, **kwargs): if hasattr(request, 'is_preview') or hasattr(request, 'show_round'): # Overriding serve method to pass submission id to get_form method copy_open_submission = request.GET.get('open_call_submission') if request.method == 'POST': draft = request.POST.get('draft', False) form = self.get_form(request.POST, request.FILES, page=self, user=request.user, draft=draft) if form.is_valid(): form_submission = self.process_form_submission(form, draft=draft) return self.render_landing_page(request, form_submission, *args, **kwargs) else: form = self.get_form(page=self, user=request.user, submission_id=copy_open_submission) context = self.get_context(request) context['form'] = form context['show_all_group_fields'] = True if copy_open_submission else False return render( request, self.get_template(request), context ) # We hide the round as only the open round is used which is displayed through the # fund page raise Http404()
def recent(self): """Find past events, most recent first. Only includes events with end date in the past.""" now = timezone.now() # construct a datetime based on now but with zero hour/minute/second today = datetime(now.year, now.month, now.day, tzinfo=timezone.get_default_timezone()) return self.filter(end_time__lt=today).order_by("-start_time") # custom manager for wagtail pages, see: # https://docs.wagtail.io/en/stable/topics/pages.html#custom-page-managers EventManager = PageManager.from_queryset(EventQuerySet) class EventTag(TaggedItemBase): """Tags for Event pages.""" content_object = ParentalKey("events.Event", on_delete=models.CASCADE, related_name="tagged_items") class Speaker(models.Model): """Relationship between Person and Event.""" event = ParentalKey("events.Event", related_name="speakers") person = models.ForeignKey(Person, on_delete=models.CASCADE)
class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore is_creatable = False submission_class = ApplicationSubmission objects = PageManager.from_queryset(RoundBaseManager)() subpage_types = [] # type: ignore lead = models.ForeignKey( settings.AUTH_USER_MODEL, limit_choices_to=LIMIT_TO_STAFF, related_name='%(class)s_lead', on_delete=models.PROTECT, ) reviewers = ParentalManyToManyField( settings.AUTH_USER_MODEL, related_name='%(class)s_reviewer', limit_choices_to=LIMIT_TO_REVIEWERS, blank=True, ) start_date = models.DateField(default=date.today) end_date = models.DateField( blank=True, null=True, default=date.today, help_text='When no end date is provided the round will remain open indefinitely.' ) sealed = models.BooleanField(default=False) content_panels = SubmittableStreamForm.content_panels + [ FieldPanel('lead'), MultiFieldPanel([ FieldRowPanel([ FieldPanel('start_date'), FieldPanel('end_date'), ]), ], heading="Dates"), FieldPanel('reviewers'), ReadOnlyPanel('get_workflow_name_display', heading="Workflow"), # Forms comes from parental key in models/forms.py ReadOnlyInlinePanel('forms', help_text="Copied from the fund."), ReadOnlyInlinePanel('review_forms', help_text="Copied from the fund."), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(SubmittableStreamForm.promote_panels, heading='Promote'), ]) def get_template(self, request, *args, **kwargs): # Make sure all children use the shared template return 'funds/round.html' def get_landing_page_template(self, request, *args, **kwargs): # Make sure all children use the shared template return 'funds/round_landing.html' @cached_property def fund(self): return self.get_parent() @property def is_sealed(self): return self.sealed and self.is_open @property def is_open(self): return self.start_date <= date.today() <= self.end_date def save(self, *args, **kwargs): is_new = not self.id if is_new and hasattr(self, 'parent_page'): parent_page = self.parent_page[self.__class__][self.title] self.workflow_name = parent_page.workflow_name self.reviewers = parent_page.reviewers.all() super().save(*args, **kwargs) if is_new and hasattr(self, 'parent_page'): # Would be nice to do this using model clusters as part of the __init__ self._copy_forms('forms') self._copy_forms('review_forms') def _copy_forms(self, field): for form in getattr(self.get_parent().specific, field).all(): new_form = self._meta.get_field(field).related_model self._copy_form(form, new_form) def _copy_form(self, form, new_class): # Create a copy of the existing form object new_form = form.form new_form.id = None new_form.name = '{} for {} ({})'.format(new_form.name, self.title, self.get_parent().title) new_form.save() new_class.objects.create(round=self, form=new_form) def get_submit_meta_data(self, **kwargs): return super().get_submit_meta_data( page=self.get_parent(), round=self, **kwargs, ) def clean(self): super().clean() if self.end_date and self.start_date > self.end_date: raise ValidationError({ 'end_date': 'End date must come after the start date', }) if self.end_date: conflict_query = ( Q(start_date__range=[self.start_date, self.end_date]) | Q(end_date__range=[self.start_date, self.end_date]) | Q(start_date__lte=self.start_date, end_date__gte=self.end_date) ) else: conflict_query = ( Q(start_date__lte=self.start_date, end_date__isnull=True) | Q(end_date__gte=self.start_date) ) if not self.id and hasattr(self, 'parent_page'): # Check if the create hook has added the parent page, we aren't an object yet. # Ensures we can access related objects during the clean phase instead of save. base_query = RoundBase.objects.child_of(self.parent_page[self.__class__][self.title]) else: # don't need parent page, we are an actual object now. base_query = RoundBase.objects.sibling_of(self) conflicting_rounds = base_query.filter( conflict_query ).exclude(id=self.id) if conflicting_rounds.exists(): error_message = mark_safe('Overlaps with the following rounds:<br> {}'.format( '<br>'.join([ f'<a href="{admin_url(round)}">{round.title}</a>: {round.start_date} - {round.end_date}' for round in conflicting_rounds] ) )) error = { 'start_date': error_message, } if self.end_date: error['end_date'] = error_message raise ValidationError(error) def serve(self, request): if hasattr(request, 'is_preview') or hasattr(request, 'show_round'): return super().serve(request) # We hide the round as only the open round is used which is displayed through the # fund page raise Http404()
data = self.file.read(256) if not data: break hash256.update(data) checksum = hash256.hexdigest() self.file.seek(original_position) return os.path.join(folder_name, checksum[:3], filename) class CustomPageQuerySet(PageQuerySet): def about_spam(self): return self.filter(title__contains='spam') CustomManager = PageManager.from_queryset(CustomPageQuerySet) class CustomManagerPage(Page): objects = CustomManager() class MyBasePage(Page): """ A base Page model, used to set site-wide defaults and overrides. """ objects = CustomManager() class Meta: abstract = True
import datetime from django.db.models import Q from wagtail.core.query import PageQuerySet from wagtail.core.models import PageManager class ActiveInactivePageQuerySet(PageQuerySet): def active(self): td = datetime.datetime.today() return self.filter(Q(expire_at__isnull=True) | Q(expire_at__gt=td)) def inactive(self): td = datetime.datetime.today() return self.filter(expire_at__lte=td) ActiveInactivePageManager = PageManager.from_queryset( ActiveInactivePageQuerySet)
"""Include only projects with the working group flag set""" return self.filter(working_group=True) def order_by_newest_grant(self): """order by grant start date, most recent grants first; secondary sort by project title""" # NOTE: using annotation to get just the most recent start date # to avoid issues with projects appearing multiple times. return self.annotate( last_start=models.Max("grants__start_date")).order_by( "-last_start", "title") # custom manager for wagtail pages, see: # https://docs.wagtail.io/en/stable/topics/pages.html#custom-page-managers ProjectManager = PageManager.from_queryset(ProjectQuerySet) class ProjectTag(TaggedItemBase): """Tags for Project pages.""" content_object = ParentalKey("projects.Project", on_delete=models.CASCADE, related_name="tagged_items") class Project(BasePage, ClusterableModel): """Page type for a CDH sponsored project or working group.""" short_description = models.CharField( max_length=255,
from v1.models import CFGOVPage from v1.models.snippets import ReusableText class JobListingPageQuerySet(PageQuerySet): def open(self): today = timezone.now().date() return self \ .filter(live=True) \ .filter(open_date__lte=today) \ .filter(close_date__gte=today) \ .order_by('close_date', 'title') JobListingPageManager = PageManager.from_queryset(JobListingPageQuerySet) LOCATION_HELP_TEXT = ("Select <strong>either</strong> one or more offices " "<strong>or</strong> one or more regions.") class JobListingPageForm(WagtailAdminPageForm): def clean(self): cleaned_data = super().clean() offices = cleaned_data.get('offices') has_offices = offices.exists() if offices else False regions = cleaned_data.get('regions') has_regions = regions.exists() if regions else False
class BlogPostQuerySet(PageQuerySet): def recent(self): """Order blog posts by date published.""" # NOTE we can't use ordering on the model to do this by default, so we # have to make sure to call this method instead. See: # https://docs.wagtail.io/en/stable/topics/pages.html#page-queryset-ordering return self.order_by("-first_published_at") def featured(self): """return blog posts that are marked as featured""" return self.filter(featured=True) # custom manager for wagtail pages, see: # https://docs.wagtail.io/en/stable/topics/pages.html#custom-page-managers BlogPostManager = PageManager.from_queryset(BlogPostQuerySet) class BlogPostTag(TaggedItemBase): """Tags for Blog posts.""" content_object = ParentalKey("blog.BlogPost", on_delete=models.CASCADE, related_name="tagged_items") class BlogPost(BasePage, ClusterableModel, PagePreviewDescriptionMixin): """A Blog post, implemented as a Wagtail page.""" featured_image = models.ForeignKey( "wagtailimages.image",
def upcoming(self): return self.end_after(localdate()) def previous(self): return self.start_before(localdate()) def date_range(self, date_range): from_date, to_date = date_range return self.end_after(from_date).start_before(to_date) def for_group(self, group): return self.filter(Q(group=group) | Q(shadow_events__group=group)) EventPageManager = PageManager.from_queryset(EventPageQuerySet) class EventPage(XrPage): template = "xr_events/pages/event_detail.html" content = StreamField( ContentBlock, blank=True, help_text=_("The content is only visible on the detail page."), ) location = models.CharField( max_length=255, blank=True, help_text=_( "Some city or address, like you would enter in GMaps or OpenStreetMap, " 'e.g. "Berlin", "Somestreet 84, 12345 Samplecity".'),
minute=0, second=0, microsecond=0) current_season = Concert.calculate_season(today) return Concert.objects.\ annotate(first_date=Min('concert_date__date')).\ annotate(last_date=Max('concert_date__date')).\ filter( last_date__lte=today, season=current_season, live=True, concert_date__date__isnull=False).distinct().\ order_by('first_date') ConcertManager = PageManager.from_queryset(ConcertQuerySet) class ConcertAdminForm(WagtailAdminPageForm): def __init__(self, data=None, files=None, parent_page=None, *args, **kwargs): super().__init__(data, files, *args, **kwargs) # Limit performer choices to those listed as performers in # child Performance pages instance = kwargs.get('instance') if instance.id: perfs = Performance.objects.live().descendant_of(self.instance)