class Batch(models.Model): event = models.ForeignKey('core.Event', on_delete=models.CASCADE, related_name='badge_batch_set') personnel_class = models.ForeignKey('labour.PersonnelClass', on_delete=models.CASCADE, null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated at')) printed_at = models.DateTimeField(null=True, blank=True) is_printed = time_bool_property('printed_at') @classmethod def create(cls, event, personnel_class=None, max_items=100, moon_rune_policy='dontcare'): from .badge import Badge if personnel_class is not None: assert personnel_class.event == event badges = Badge.objects.filter(personnel_class=personnel_class) else: badges = Badge.objects.filter(personnel_class__event=event) if moon_rune_policy == 'onlyinclude': fields = Badge.get_csv_fields(event) test = lambda badge: contains_moon_runes( badge.get_printable_text(fields)) elif moon_rune_policy == 'exclude': fields = Badge.get_csv_fields(event) test = lambda badge: not contains_moon_runes( badge.get_printable_text(fields)) elif moon_rune_policy == 'dontcare': test = lambda badge: True else: raise NotImplementedError(moon_rune_policy) with transaction.atomic(): batch = cls(personnel_class=personnel_class, event=event) batch.save() badges = badges.filter( **BADGE_ELIGIBLE_FOR_BATCHING).order_by('created_at') num_selected_badges = 0 for badge in badges: if test(badge): badge.batch = batch badge.save() num_selected_badges += 1 if max_items is not None and num_selected_badges >= max_items: break return batch def confirm(self): self.printed_at = now() self.save() def cancel(self): self.badges.update(batch=None) self.delete() def can_cancel(self): return self.printed_at is not None def can_confirm(self): return self.printed_at is not None def __str__(self): return _("Batch %(batch_number)s") % dict(batch_number=self.pk) def admin_get_number(self): return text_type(self) admin_get_number.short_description = _('Batch number') admin_get_number.admin_order_field = 'id' def admin_get_num_badges(self): return self.badges.count() admin_get_num_badges.short_description = _('Number of badges') class Meta: verbose_name = _('Batch') verbose_name_plural = _('Batches')
class Signup(models.Model, CsvExportMixin): person = models.ForeignKey('core.Person', related_name='signups') event = models.ForeignKey('core.Event') personnel_classes = models.ManyToManyField( 'labour.PersonnelClass', blank=True, verbose_name='Henkilöstöluokat', help_text= 'Mihin henkilöstöryhmiin tämä henkilö kuuluu? Henkilö saa valituista ryhmistä ' 'ylimmän mukaisen badgen.', ) job_categories = models.ManyToManyField( 'labour.JobCategory', verbose_name='Haettavat tehtävät', help_text= 'Valitse kaikki ne tehtävät, joissa olisit valmis työskentelemään ' 'tapahtumassa. Huomaathan, että sinulle tarjottavia tehtäviä voi rajoittaa se, ' 'mitä pätevyyksiä olet ilmoittanut sinulla olevan. Esimerkiksi järjestyksenvalvojaksi ' 'voivat ilmoittautua ainoastaan JV-kortilliset.', related_name='signup_set') notes = models.TextField( blank=True, verbose_name='Käsittelijän merkinnät', help_text= ('Tämä kenttä ei normaalisti näy henkilölle itselleen, mutta jos tämä ' 'pyytää henkilörekisteriotetta, kentän arvo on siihen sisällytettävä.' ), ) created_at = models.DateTimeField(auto_now_add=True, verbose_name='Luotu') updated_at = models.DateTimeField(auto_now=True, verbose_name='Päivitetty') job_categories_accepted = models.ManyToManyField( 'labour.JobCategory', blank=True, related_name='accepted_signup_set', verbose_name='Hyväksytyt tehtäväalueet', help_text= 'Tehtäväalueet, joilla hyväksytty vapaaehtoistyöntekijä tulee työskentelemään. ' 'Tämän perusteella henkilölle mm. lähetetään oman tehtäväalueensa työvoimaohjeet. ' 'Harmaalla merkityt tehtäväalueet ovat niitä, joihin hakija ei ole itse hakenut.' ) job_categories_rejected = models.ManyToManyField( 'labour.JobCategory', blank=True, related_name='+', verbose_name=_('Rejected job categories'), help_text= _('The workforce manager may use this field to inform other workforce managers that ' 'this applicant will not be accepted to a certain job category. This field is not visible ' 'to the applicant, but should they request a record of their own information, this field will ' 'be included.')) xxx_interim_shifts = models.TextField( blank=True, null=True, default="", verbose_name="Työvuorot", help_text= ("Tämä tekstikenttä on väliaikaisratkaisu, jolla vänkärin työvuorot voidaan " "merkitä Kompassiin ja lähettää vänkärille työvoimaviestissä jo ennen kuin " "lopullinen työvuorotyökalu on käyttökunnossa."), ) alternative_signup_form_used = models.ForeignKey( 'labour.AlternativeSignupForm', blank=True, null=True, verbose_name="Ilmoittautumislomake", help_text= ("Tämä kenttä ilmaisee, mitä ilmoittautumislomaketta hakemuksen täyttämiseen käytettiin. " "Jos kenttä on tyhjä, käytettiin oletuslomaketta."), ) job_title = models.CharField( max_length=JOB_TITLE_LENGTH, blank=True, default='', verbose_name="Tehtävänimike", help_text= ("Printataan badgeen ym. Asetetaan automaattisesti hyväksyttyjen tehtäväalueiden perusteella, " "mikäli kenttä jätetään tyhjäksi."), ) is_active = models.BooleanField(verbose_name='Aktiivinen', default=True) time_accepted = models.DateTimeField( null=True, blank=True, verbose_name='Hyväksytty', ) time_confirmation_requested = models.DateTimeField( null=True, blank=True, verbose_name='Vahvistusta vaadittu', ) time_finished = models.DateTimeField( null=True, blank=True, verbose_name='Vuorot valmiit', ) time_complained = models.DateTimeField( null=True, blank=True, verbose_name='Vuoroista reklamoitu', ) time_cancelled = models.DateTimeField( null=True, blank=True, verbose_name='Peruutettu', ) time_rejected = models.DateTimeField( null=True, blank=True, verbose_name='Hylätty', ) time_arrived = models.DateTimeField( null=True, blank=True, verbose_name='Saapunut tapahtumaan', ) time_work_accepted = models.DateTimeField( null=True, blank=True, verbose_name='Työpanos hyväksytty', ) time_reprimanded = models.DateTimeField( null=True, blank=True, verbose_name='Työpanoksesta esitetty moite', ) is_accepted = time_bool_property('time_accepted') is_confirmation_requested = time_bool_property( 'time_confirmation_requested') is_finished = time_bool_property('time_finished') is_complained = time_bool_property('time_complained') is_cancelled = time_bool_property('time_cancelled') is_rejected = time_bool_property('time_rejected') is_arrived = time_bool_property('time_arrived') is_work_accepted = time_bool_property('time_work_accepted') is_workaccepted = alias_property( 'is_work_accepted') # for automagic groupiness is_reprimanded = time_bool_property('time_reprimanded') is_new = property(lambda self: self.state == 'new') is_applicants = alias_property( 'is_active') # group is called applicants for historical purposes is_confirmation = alias_property('is_confirmation_requested') is_processed = property(lambda self: self.state != 'new') class Meta: verbose_name = _('signup') verbose_name_plural = _('signups') def __str__(self): p = self.person.full_name if self.person else 'None' e = self.event.name if self.event else 'None' return '{p} / {e}'.format(**locals()) @property def personnel_class(self): """ The highest personnel class of this Signup (possibly None). """ return self.personnel_classes.first() @property def signup_extra_model(self): return self.event.labour_event_meta.signup_extra_model @property def signup_extra(self): if not hasattr(self, '_signup_extra'): SignupExtra = self.signup_extra_model self._signup_extra = SignupExtra.for_signup(self) return self._signup_extra def get_first_categories(self): return self.job_categories.all()[:NUM_FIRST_CATEGORIES] @property def is_more_categories(self): return self.job_categories.count() > NUM_FIRST_CATEGORIES def get_redacted_category_names(self): return ', '.join( cat.name for cat in self.job_categories.all()[NUM_FIRST_CATEGORIES:]) @property def job_categories_label(self): if self.state == 'new': return 'Haetut tehtävät' else: return 'Hyväksytyt tehtävät' @property def job_category_accepted_labels(self): state = self.state label_class = SIGNUP_STATE_LABEL_CLASSES[state] if state == 'new': label_texts = [cat.name for cat in self.get_first_categories()] labels = [(label_class, label_text, None) for label_text in label_texts] if self.is_more_categories: labels.append( (label_class, '...', self.get_redacted_category_names())) elif state == 'cancelled': labels = [(label_class, 'Peruutettu', None)] elif state == 'rejected': labels = [(label_class, 'Hylätty', None)] elif state == 'beyond_logic': labels = [(label_class, 'Perätilassa', None)] elif self.is_accepted: label_texts = [ cat.name for cat in self.job_categories_accepted.all() ] labels = [(label_class, label_text, None) for label_text in label_texts] else: from warnings import warn warn('Unknown state: {state}'.format(self=self)) labels = [] return labels @property def personnel_class_labels(self): label_texts = [pc.name for pc in self.personnel_classes.all()] return [('label-default', label_text, None) for label_text in label_texts] @property def some_job_title(self): """ Tries to figure a job title for this worker using the following methods in this order 1. A manually set job title 2. The title of the job category the worker is accepted into 3. A generic job title """ if self.job_title: return self.job_title elif self.job_categories_accepted.exists(): return self.job_categories_accepted.first().name else: return 'Työvoima' @property def granted_privileges(self): if 'access' not in settings.INSTALLED_APPS: return [] from access.models import GrantedPrivilege return GrantedPrivilege.objects.filter( person=self.person, privilege__group_privileges__group__in=self.person.user.groups.all( ), privilege__group_privileges__event=self.event, ) @property def potential_privileges(self): if 'access' not in settings.INSTALLED_APPS: return [] from access.models import Privilege return Privilege.get_potential_privileges( person=self.person, group_privileges__event=self.event) @classmethod def get_or_create_dummy(cls, accepted=False): from core.models import Person, Event from .job_category import JobCategory person, unused = Person.get_or_create_dummy() event, unused = Event.get_or_create_dummy() job_category, unused = JobCategory.get_or_create_dummy() signup, created = Signup.objects.get_or_create(person=person, event=event) extra = signup.signup_extra signup.job_categories = [job_category] extra.save() if accepted: signup.job_categories_accepted = signup.job_categories.all() signup.personnel_classes.add( signup.job_categories.first().personnel_classes.first()) signup.state = 'accepted' signup.save() signup.apply_state() return signup, created @classmethod def get_state_query_params(cls, state): flag_values = STATE_FLAGS_BY_NAME[state] assert len(STATE_TIME_FIELDS) == len(flag_values) query_params = [] for time_field_name, flag_value in zip(STATE_TIME_FIELDS, flag_values): time_field_preposition = '{}__isnull'.format(time_field_name) query_params.append((time_field_preposition, not flag_value)) # First state flag is not a time bool field, but an actual bona fide boolean field. # Also "is null" semantics mean that flag values are flipped, so we need to backflip it. query_params[0] = ('is_active', not query_params[0][1]) return OrderedDict(query_params) @classmethod def mass_reject(cls, signups): return cls._mass_state_change('new', 'rejected', signups) @classmethod def mass_request_confirmation(cls, signups): return cls._mass_state_change('accepted', 'confirmation', signups) @classmethod def filter_signups_for_mass_send_shifts(cls, signups): return signups.filter( **cls.get_state_query_params('accepted')).exclude( xxx_interim_shifts='', shifts__isnull=True, ) @classmethod def mass_send_shifts(cls, signups): return cls._mass_state_change( old_state='accepted', new_state='finished', signups=signups, filter_func=cls.filter_signups_for_mass_send_shifts) @classmethod def _mass_state_change(cls, old_state, new_state, signups, filter_func=None): if filter_func is None: signups = signups.filter( **Signup.get_state_query_params(old_state)) else: signups = filter_func(signups) for signup in signups: signup.state = new_state signup.save() signup.apply_state() return signups def apply_state(self): self.apply_state_sync() if 'background_tasks' in settings.INSTALLED_APPS: from ..tasks import signup_apply_state signup_apply_state.delay(self.pk) else: self._apply_state() def apply_state_sync(self): self.apply_state_ensure_job_categories_accepted_is_set() self.apply_state_ensure_personnel_class_is_set() self.signup_extra.apply_state() self.apply_state_create_badges() def _apply_state(self): self.apply_state_group_membership() self.apply_state_email_aliases() self.apply_state_send_messages() def apply_state_group_membership(self): from .job_category import JobCategory from .personnel_class import PersonnelClass groups_to_add = set() groups_to_remove = set() for group_suffix in SIGNUP_STATE_GROUPS: should_belong_to_group = getattr( self, 'is_{group_suffix}'.format(group_suffix=group_suffix)) group = self.event.labour_event_meta.get_group(group_suffix) if should_belong_to_group: groups_to_add.add(group) else: groups_to_remove.add(group) for job_category in JobCategory.objects.filter(event=self.event): should_belong_to_group = self.job_categories_accepted.filter( pk=job_category.pk).exists() group = self.event.labour_event_meta.get_group(job_category.slug) if should_belong_to_group: groups_to_add.add(group) else: groups_to_remove.add(group) for personnel_class in PersonnelClass.objects.filter( event=self.event, app_label='labour'): should_belong_to_group = self.personnel_classes.filter( pk=personnel_class.pk).exists() group = self.event.labour_event_meta.get_group( personnel_class.slug) if should_belong_to_group: groups_to_add.add(group) else: groups_to_remove.add(group) ensure_user_group_membership(self.person.user, groups_to_add, groups_to_remove) def apply_state_email_aliases(self): if 'access' not in settings.INSTALLED_APPS: return from access.models import GroupEmailAliasGrant GroupEmailAliasGrant.ensure_aliases(self.person) def apply_state_send_messages(self, resend=False): if 'mailings' not in settings.INSTALLED_APPS: return from mailings.models import Message Message.send_messages(self.event, 'labour', self.person) def apply_state_ensure_job_categories_accepted_is_set(self): if self.is_accepted and not self.job_categories_accepted.exists( ) and self.job_categories.count() == 1: self.job_categories_accepted.add(self.job_categories.get()) def apply_state_ensure_personnel_class_is_set(self): for app_label in self.job_categories_accepted.values_list( 'app_label', flat=True).distinct(): if self.personnel_classes.filter(app_label=app_label).exists(): continue any_jca = self.job_categories_accepted.filter( app_label=app_label).first() personnel_class = any_jca.personnel_classes.first() self.personnel_classes.add(personnel_class) def apply_state_create_badges(self): if 'badges' not in settings.INSTALLED_APPS: return if self.event.badges_event_meta is None: return from badges.models import Badge Badge.ensure(event=self.event, person=self.person) def get_previous_and_next_signup(self): queryset = self.event.signup_set.order_by('person__surname', 'person__first_name', 'id').all() return get_previous_and_next(queryset, self) @property def _state_flags(self): # The Grand Order is defined here. return ( self.is_active, self.is_accepted, self.is_confirmation_requested, self.is_finished, self.is_complained, self.is_arrived, self.is_work_accepted, self.is_reprimanded, self.is_rejected, self.is_cancelled, ) @_state_flags.setter def _state_flags(self, flags): # These need to be in the Grand Order. ( self.is_active, self.is_accepted, self.is_confirmation_requested, self.is_finished, self.is_complained, self.is_arrived, self.is_work_accepted, self.is_reprimanded, self.is_rejected, self.is_cancelled, ) = flags @property def state(self): return STATE_NAME_BY_FLAGS[self._state_flags] @state.setter def state(self, new_state): self._state_flags = STATE_FLAGS_BY_NAME[new_state] @property def next_states(self): cur_state = self.state states = [] if cur_state == 'new': states.extend(('accepted', 'rejected', 'cancelled')) elif cur_state == 'accepted': states.extend(('finished', 'confirmation', 'cancelled')) elif cur_state == 'confirmation': states.extend(('accepted', 'cancelled')) elif cur_state == 'finished': states.extend(('arrived', 'complained', 'no_show', 'relieved')) elif cur_state == 'complained': states.extend(('finished', 'relieved')) elif cur_state == 'arrived': states.extend(('honr_discharged', 'dish_discharged', 'relieved')) elif cur_state == 'beyond_logic': states.extend(( 'new', 'accepted', 'finished', 'complained', 'rejected', 'cancelled', 'arrived', 'honr_discharged', 'no_show', )) if cur_state != 'beyond_logic': states.append('beyond_logic') return states @property def next_states_buttons(self): return [ StateTransition(self, to_state) for to_state in self.next_states ] @property def formatted_state(self): return dict(SIGNUP_STATE_NAMES).get(self.state, '') @property def state_label_class(self): return SIGNUP_STATE_LABEL_CLASSES[self.state] @property def state_description(self): return SIGNUP_STATE_DESCRIPTIONS.get(self.state, '') @property def state_times(self): return [( self._meta.get_field(field_name).verbose_name, getattr(self, field_name, None), ) for field_name in STATE_TIME_FIELDS if getattr(self, field_name, None)] @property def person_messages(self): if 'mailings' in settings.INSTALLED_APPS: if getattr(self, '_person_messages', None) is None: self._person_messages = self.person.personmessage_set.filter( message__recipient__event=self.event, message__recipient__app_label='labour', ).order_by('-created_at') return self._person_messages else: return [] @property def have_person_messages(self): if 'mailings' in settings.INSTALLED_APPS: return self.person_messages.exists() else: return False @property def applicant_has_actions(self): return any([ self.applicant_can_edit, self.applicant_can_confirm, self.applicant_can_cancel, ]) @property def applicant_can_edit(self): return self.state == 'new' and self.is_registration_open @property def is_registration_open(self): if self.alternative_signup_form_used is not None: return self.alternative_signup_form_used.is_active else: return self.event.labour_event_meta.is_registration_open @property def applicant_can_confirm(self): return self.state == 'confirmation' def confirm(self): assert self.state == 'confirmation' self.state = 'accepted' self.save() self.apply_state() @property def applicant_can_cancel(self): return self.is_active and not self.is_cancelled and not self.is_rejected and \ not self.is_arrived @property def formatted_personnel_classes(self): from .job_category import format_job_categories return format_job_categories(self.personnel_classes.all()) @property def formatted_job_categories_accepted(self): from .job_category import format_job_categories return format_job_categories(self.job_categories_accepted.all()) @property def formatted_job_categories(self): from .job_category import format_job_categories return format_job_categories(self.job_categories.all()) @property def formatted_shifts(self): parts = [] if self.xxx_interim_shifts: parts.append(self.xxx_interim_shifts) parts.extend(text_type(shift) for shift in self.shifts.all()) return "\n\n".join(part for part in parts if part) # for admin @property def full_name(self): return self.person.full_name @property def info_links(self): from .info_link import InfoLink return InfoLink.objects.filter( event=self.event, group__user=self.person.user, ) @property def email_address(self): from access.models import EmailAlias email_alias = EmailAlias.objects.filter( type__domain__organization=self.event.organization, person=self.person, ).order_by('type__priority').first() # TODO order return email_alias.email_address if email_alias else self.person.email @classmethod def get_csv_fields(cls, event): if getattr(event, '_signup_csv_fields', None) is None: from core.models import Person event._signup_csv_fields = [] related_models = [Person, Signup] fields_to_skip = [ # useless & non-serializable (Person, 'user'), (Signup, 'person'), # too official (Person, 'official_first_names'), (Person, 'muncipality'), ] SignupExtra = event.labour_event_meta.signup_extra_model if SignupExtra is not None: related_models.append(SignupExtra) fields_to_skip.extend([ (SignupExtra, 'event'), (SignupExtra, 'person'), ]) # XXX HACK jv-kortin numero if 'labour_common_qualifications' in settings.INSTALLED_APPS: from labour_common_qualifications.models import JVKortti related_models.append(JVKortti) fields_to_skip.append((JVKortti, 'personqualification')) for model in related_models: for field in model._meta.fields: if (model, field.name) in fields_to_skip: continue event._signup_csv_fields.append((model, field)) for field in model._meta.many_to_many: event._signup_csv_fields.append((model, field)) return event._signup_csv_fields def get_csv_related(self): from core.models import Person related = {Person: self.person} signup_extra_model = self.signup_extra_model if signup_extra_model: related[signup_extra_model] = self.signup_extra # XXX HACK jv-kortin numero if 'labour_common_qualifications' in settings.INSTALLED_APPS: from labour_common_qualifications.models import JVKortti try: jv_kortti = JVKortti.objects.get( personqualification__person=self.person) related[JVKortti] = jv_kortti except JVKortti.DoesNotExist: related[JVKortti] = None return related def as_dict(self): # XXX? signup_extra = self.signup_extra shift_wishes = signup_extra.shift_wishes if signup_extra.get_field( 'shift_wishes') else '' total_work = signup_extra.total_work if signup_extra.get_field( 'total_work') else '' shift_type = signup_extra.get_shift_type_display( ) if signup_extra.get_field('shift_type') else '' return dict( id=self.person.id, fullName=self.person.full_name, shiftWishes=shift_wishes, totalWork=total_work, currentlyAssigned=self.shifts.all().aggregate( sum_hours=Coalesce(Sum('hours'), 0))['sum_hours'], shiftType=shift_type, ) @classmethod def for_signup(cls, signup): """ Surveys make use of this method. """ return signup
class Badge(models.Model, CsvExportMixin): person = models.ForeignKey( 'core.Person', null=True, blank=True, verbose_name=_('Person'), ) personnel_class = models.ForeignKey( 'labour.PersonnelClass', verbose_name=_('Personnel class'), ) printed_separately_at = models.DateTimeField( null=True, blank=True, verbose_name=_('Printed separately at'), ) revoked_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, related_name='badges_revoked', verbose_name=_('Revoked by'), ) revoked_at = models.DateTimeField( null=True, blank=True, verbose_name=_('Revoked at'), ) arrived_at = models.DateTimeField( null=True, blank=True, verbose_name=_('Arrived at'), ) first_name = models.CharField( blank=True, max_length=1023, verbose_name=_('First name'), ) is_first_name_visible = models.BooleanField( default=True, verbose_name=_('Is first name visible'), ) surname = models.CharField( blank=True, max_length=1023, verbose_name=_('Surname'), ) is_surname_visible = models.BooleanField( default=True, verbose_name=_('Is surname visible'), ) nick = models.CharField( blank=True, max_length=1023, verbose_name=_('Nick name'), help_text= _('If you only have a single piece of information to print on the badge, use this field.' ), ) is_nick_visible = models.BooleanField( default=True, verbose_name=_('Is nick visible'), ) job_title = models.CharField( max_length=63, blank=True, default='', verbose_name=_('Job title'), help_text=_('Please stay civil with the job title field.'), ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, null=True, blank=True, related_name='badges_created', verbose_name=_('Created by'), ) created_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Created at'), ) updated_at = models.DateTimeField( auto_now=True, verbose_name=_('Updated at'), ) batch = models.ForeignKey( 'badges.Batch', null=True, blank=True, db_index=True, verbose_name=_('Printing batch'), on_delete=models.SET_NULL, ) is_revoked = time_bool_property('revoked_at') is_printed = time_bool_property('printed_at') is_printed_separately = time_bool_property('printed_separately_at') is_arrived = time_bool_property('arrived_at') @property def printed_at(self): if self.printed_separately_at: return self.printed_separately_at elif self.batch: return self.batch.printed_at else: return None @property def formatted_printed_at(self): # XXX not really "formatted" return self.printed_at if self.printed_at is not None else '' @classmethod def get_or_create_dummy(cls): from core.models import Person from labour.models import PersonnelClass person, unused = Person.get_or_create_dummy() personnel_class, unused = PersonnelClass.get_or_create_dummy() return cls.objects.get_or_create( person=person, personnel_class=personnel_class, ) @classmethod def ensure(cls, event, person): """ Makes sure the person has a badge of the correct class and up-to-date information for a given event. """ from badges.utils import default_badge_factory assert person is not None with transaction.atomic(): try: existing_badge = cls.objects.get( personnel_class__event=event, person=person, revoked_at__isnull=True, ) except cls.DoesNotExist: existing_badge = None expected_badge_opts = default_badge_factory(event=event, person=person) if existing_badge: # There is an existing un-revoked badge. Check that its information is correct. if any( getattr(existing_badge, key) != value for key, value in expected_badge_opts.items()): existing_badge.revoke() else: return existing_badge, False if expected_badge_opts.get('personnel_class') is None: # They should not have a badge. return None, False badge_opts = dict(expected_badge_opts, person=person) badge = cls(**badge_opts) badge.save() return badge, True @classmethod def get_csv_fields(cls, event): from labour.models import PersonnelClass meta = event.badges_event_meta if meta.badge_layout == 'trad': # Chief Technology Officer # Santtu Pajukanta # Japsu return [ (cls, 'personnel_class_name'), (BadgePrivacyAdapter, 'surname'), (BadgePrivacyAdapter, 'first_name'), (BadgePrivacyAdapter, 'nick'), (cls, 'job_title'), ] elif meta.badge_layout == 'nick': # JAPSU # Santtu Pajukanta # Chief Technology Officer # -OR- # SANTTU # Pajukanta # Chief Technology Officer return [ (cls, 'personnel_class_name'), (BadgePrivacyAdapter, 'nick_or_first_name'), (BadgePrivacyAdapter, 'surname_or_full_name'), (cls, 'job_title'), ] else: raise NotImplementedError(meta.badge_layout) def get_csv_related(self): from core.models import Person return { BadgePrivacyAdapter: BadgePrivacyAdapter(self), } def get_name_fields(self): return [ (self.surname.strip(), self.is_surname_visible), (self.first_name.strip(), self.is_first_name_visible), (self.nick.strip(), self.is_nick_visible), ] @property def personnel_class_name(self): return self.personnel_class.name if self.personnel_class else '' @property def event(self): return self.personnel_class.event @property def meta(self): return self.event.badges_event_meta @property def signup(self): from labour.models import Signup if self.person is None: return None return Signup.objects.filter(event=self.event, person=self.person).first() @property def signup_extra(self): if not hasattr(self, '_signup_extra'): if self.person_id is None or self.event.labour_event_meta is None: self._signup_extra = None else: SignupExtra = self.event.labour_event_meta.signup_extra_model try: self._signup_extra = SignupExtra.get_for_event_and_person( self.event, self.person) except SignupExtra.DoesNotExist: self._signup_extra = None return self._signup_extra @property def event_name(self): return self.personnel_class.event.name if self.personnel_class else '' def get_printable_text(self, fields): return '\n'.join( str(value) for value in self.get_csv_row(self.event, fields, 'comma_separated')) def to_html_print(self): def format_name_field(value, is_visible): if is_visible: return '<strong>{value}</strong>'.format(value=escape(value)) else: return escape(value) vars = dict( surname=format_name_field(self.surname.strip(), self.is_surname_visible), first_name=format_name_field(self.first_name.strip(), self.is_first_name_visible), nick=format_name_field(self.nick.strip(), self.is_nick_visible), ) if self.nick: return "{surname}, {first_name}, {nick}".format(**vars) else: return "{surname}, {first_name}".format(**vars) def revoke(self, user=None): """ Revoke the badge. When a badge that is not yet assigned to a batch or printed separately is revoked, it is removed altogether. When a badge that is already assigned to a batch or printed separately is revoked, it will be marked as such but not removed, because it needs to be manually removed from distribution. Note that the batch does not need to be marked as printed yet for a badge to stay around revoked, because a batch that is already created but not yet printed may have been downloaded as Excel already. A Batch should never change after being created. """ assert not self.is_revoked if self.is_printed_separately or self.batch: self.is_revoked = True self.revoked_by = user self.save() return self else: self.delete() return None def unrevoke(self): assert self.is_revoked self.is_revoked = False self.revoked_by = None self.save() return self def admin_get_full_name(self): if self.nick: return '{self.first_name} "{self.nick}" {self.surname}'.format( self=self) else: return '{self.first_name} {self.surname}'.format(self=self) admin_get_full_name.short_description = _('Name') admin_get_full_name.admin_order_field = ('surname', 'first_name', 'nick') def __str__(self): return "{person_name} ({personnel_class_name}, {event_name})".format( person_name=self.admin_get_full_name(), personnel_class_name=self.personnel_class_name, event_name=self.event_name, )