def start_of_next_business_year(): day = Config.business_year_start()["day"] month = Config.business_year_start()["month"] return calculate_next(day, month)
class Meta: verbose_name = _('{0}-Typ').format(Config.vocabulary('subscription')) verbose_name_plural = _('{0}-Typen').format( Config.vocabulary('subscription'))
def get_server(): return 'http://' + Config.adminportal_server_url()
def handle(self, *args, **options): subscription = Subscription.objects.all()[0] if ExtraSubscription.objects.all().count() > 0: extrasub = ExtraSubscription.objects.all()[0] else: extrasub = None share = Share.objects.all()[0] job = RecuringJob.objects.all()[0] member = Member.objects.all()[0] depot = Depot.objects.all()[0] bill = Bill(ref_number='123456789', amount='1234.99') print('*** welcome mit abo***') plaintext = get_template(Config.emails('welcome')) d = { 'username': '******', 'password': '******', 'hash': 'hash', 'subscription': subscription, 'serverurl': get_server() } content = plaintext.render(d) print(content) print() print('*** welcome ohne abo***') plaintext = get_template(Config.emails('welcome')) d = { 'username': '******', 'password': '******', 'hash': 'hash', 'subscription': None, 'serverurl': get_server() } content = plaintext.render(d) print(content) print() print('*** s_created ***') plaintext = get_template(Config.emails('s_created')) d = {'share': share, 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** n_sub ***') plaintext = get_template(Config.emails('n_sub')) d = {'subscription': subscription, 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** co_welcome ***') plaintext = get_template(Config.emails('co_welcome')) d = { 'username': '******', 'name': 'Hans Muster', 'password': '******', 'hash': 'hash', 'shares': '9', 'serverurl': get_server() } content = plaintext.render(d) print(content) print() print('*** co_added ***') plaintext = get_template(Config.emails('co_added')) d = { 'username': '******', 'name': 'Hans Muster', 'password': '******', 'hash': 'hash', 'shares': '9', 'serverurl': get_server() } content = plaintext.render(d) print(content) print() print('*** password ***') plaintext = get_template(Config.emails('password')) d = { 'email': '*****@*****.**', 'password': '******', 'serverurl': get_server() } content = plaintext.render(d) print(content) print() print('*** j_reminder ***') plaintext = get_template(Config.emails('j_reminder')) coordinator = job.type.activityarea.coordinator contact = coordinator.first_name + ' ' + coordinator.last_name + ': ' + job.type.activityarea.contact( ) d = { 'job': job, 'participants': [member], 'serverurl': get_server(), 'contact': contact } content = plaintext.render(d) print(content) print() print('*** j_canceled ***') plaintext = get_template(Config.emails('j_canceled')) d = {'job': job, 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** confirm ***') plaintext = get_template(Config.emails('confirm')) d = {'hash': 'hash', 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** j_changed ***') plaintext = get_template(Config.emails('j_changed')) d = {'job': job, 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** j_signup ***') plaintext = get_template(Config.emails('j_signup')) d = {'job': job, 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** d_changed ***') plaintext = get_template(Config.emails('d_changed')) d = {'depot': depot, 'serverurl': get_server()} content = plaintext.render(d) print(content) print() print('*** s_canceled ***') plaintext = get_template(Config.emails('s_canceled')) d = { 'subscription': subscription, 'message': 'Nachricht', } content = plaintext.render(d) print(content) print() print('*** m_canceled ***') plaintext = get_template(Config.emails('m_canceled')) d = { 'member': member, 'end_date': timezone.now(), 'message': 'Nachricht', } content = plaintext.render(d) print(content) print() print('*** b_share ***') plaintext = get_template(Config.emails('b_share')) d = { 'member': member, 'bill': bill, 'share': share, 'serverurl': get_server() } content = plaintext.render(d) print(content) print() print('*** b_sub ***') plaintext = get_template(Config.emails('b_sub')) d = { 'member': member, 'bill': bill, 'sub': subscription, 'start': timezone.now(), 'end': timezone.now(), 'serverurl': get_server() } content = plaintext.render(d) print(content) print() if extrasub is not None: print('*** b_esub ***') plaintext = get_template(Config.emails('b_esub')) d = { 'member': member, 'bill': bill, 'extrasub': extrasub, 'start': timezone.now(), 'end': timezone.now(), 'serverurl': get_server() } content = plaintext.render(d) print(content) print()
def __str__(self): return '%s #%s' % (Config.vocabulary('assignment'), self.id)
def images(key): return Config.images(key)
def cookie_consent(key): return Config.cookie_consent(key)
class Meta: verbose_name = Config.vocabulary('member') verbose_name_plural = Config.vocabulary('member_pl') permissions = (('can_filter_members', _('Benutzer kann {0} filtern').format(Config.vocabulary('member_pl'))),)
def share_created(share): EmailSender.get_sender( organisation_subject( _('Neue/r/s {} erstellt').format(Config.vocabulary('share'))), get_email_content('a_share_created', base_dict(locals())), bcc=get_emails_by_permission('notified_on_share_creation')).send()
def member_canceled(member, end_date, message, **kwargs): EmailSender.get_sender(organisation_subject( _('{} gekündigt').format(Config.vocabulary('member_type'))), get_email_content('m_canceled', base_dict(locals())), bcc=kwargs['emails']).send()
class Member(JuntagricoBaseModel): ''' Additional fields for Django's default user class. ''' # user class is only used for logins, permissions, and other builtin django stuff # all user information should be stored in the Member model user = models.OneToOneField( User, related_name='member', null=True, blank=True, on_delete=models.CASCADE) first_name = models.CharField(_('Vorname'), max_length=30) last_name = models.CharField(_('Nachname'), max_length=30) email = models.EmailField(unique=True) addr_street = models.CharField(_('Strasse'), max_length=100) addr_zipcode = models.CharField(_('PLZ'), max_length=10) addr_location = models.CharField(_('Ort'), max_length=50) birthday = models.DateField(_('Geburtsdatum'), null=True, blank=True) phone = models.CharField(_('Telefonnr'), max_length=50) mobile_phone = models.CharField( _('Mobile'), max_length=50, null=True, blank=True) iban = models.CharField('IBAN', max_length=100, blank=True, default='') future_subscription = models.ForeignKey( 'Subscription', related_name='members_future', null=True, blank=True, on_delete=models.SET_NULL) subscription = models.ForeignKey( 'Subscription', related_name='members', null=True, blank=True, on_delete=models.SET_NULL) old_subscriptions = models.ManyToManyField( 'Subscription', related_name='members_old') confirmed = models.BooleanField(_('E-Mail-Adresse bestätigt'), default=False) reachable_by_email = models.BooleanField( _('Kontaktierbar von der Job Seite aus'), default=False) canceled = models.BooleanField(_('gekündigt'), default=False) cancelation_date = models.DateField( _('Kündigüngsdatum'), null=True, blank=True) end_date = models.DateField( _('Enddatum'), null=True, blank=True) inactive = models.BooleanField(_('inaktiv'), default=False, help_text=_('Sperrt Login und entfernt von E-Mail-Listen')) notes = models.TextField( _('Notizen'), max_length=1000, blank=True, help_text=_('Notizen für Administration. Nicht sichtbar für {}'.format(Config.vocabulary('member')))) @property def active_shares(self): """ :return: shares that have been paid by member and not cancelled AND paid back yet """ return self.share_set.filter(paid_date__isnull=False).filter(payback_date__isnull=True) @property def active_shares_count(self): return self.active_shares.count() @property def is_cooperation_member(self): return self.active_shares_count > 0 @property def usable_shares(self): """ :return: shares that have been ordered (i.e. created) and not cancelled yet """ return self.share_set.filter(cancelled_date__isnull=True) @property def usable_shares_count(self): return self.usable_shares.count() @property def in_subscription(self): return (self.future_subscription is not None) | (self.subscription is not None) @property def blocked(self): future = self.future_subscription is not None current = self.subscription is None or not self.subscription.canceled return future or not current def get_name(self): return '%s %s' % (self.first_name, self.last_name) def get_phone(self): if self.mobile_phone != '': return self.mobile_phone return self.phone def get_hash(self): return hashlib.sha1((str(self.email) + str(self.pk)).encode('utf8')).hexdigest() def __str__(self): return self.get_name() def clean(self): check_member_consistency(self) @classmethod def create(cls, sender, instance, created, **kwds): ''' Callback to create corresponding user when new member is created. ''' if created and instance.user is None: username = make_username( instance.first_name, instance.last_name, instance.email) user = User(username=username) user.save() user = User.objects.get(username=username) instance.user = user instance.save() @classmethod def post_delete(cls, sender, instance, **kwds): instance.user.delete() @notifiable class Meta: verbose_name = Config.vocabulary('member') verbose_name_plural = Config.vocabulary('member_pl') permissions = (('can_filter_members', _('Benutzer kann {0} filtern').format(Config.vocabulary('member_pl'))),)
def member_created(member, **kwargs): EmailSender.get_sender(organisation_subject( _('Neue/r/s {}').format(Config.vocabulary('member_type'))), get_email_content('a_member_created', base_dict(locals())), bcc=kwargs['emails']).send()
def share_created(share, **kwargs): EmailSender.get_sender(organisation_subject( _('Neue/r/s {} erstellt').format(Config.vocabulary('share'))), get_email_content('a_share_created', base_dict(locals())), bcc=kwargs['emails']).send()
def subscription_canceled(subscription, message, **kwargs): EmailSender.get_sender(organisation_subject( _('{} gekündigt').format(Config.vocabulary('subscription'))), get_email_content('s_canceled', base_dict(locals())), bcc=kwargs['emails']).send()
class Subscription(Billable): ''' One Subscription that may be shared among several people. ''' depot = models.ForeignKey('Depot', on_delete=models.PROTECT, related_name='subscription_set') future_depot = models.ForeignKey( Depot, on_delete=models.PROTECT, related_name='future_subscription_set', null=True, blank=True, verbose_name=_('Zukünftiges {}').format(Config.vocabulary('depot')), help_text='Nur setzen, wenn {} geändert werden soll'.format( Config.vocabulary('depot'))) types = models.ManyToManyField('SubscriptionType', through='TSST', related_name='subscription_set') future_types = models.ManyToManyField( 'SubscriptionType', through='TFSST', related_name='future_subscription_set') primary_member = models.ForeignKey( 'Member', related_name='subscription_primary', null=True, blank=True, on_delete=models.PROTECT, verbose_name=_('Haupt-{}-BezieherIn').format( Config.vocabulary('subscription'))) active = models.BooleanField(default=False, verbose_name='Aktiv') canceled = models.BooleanField(_('gekündigt'), default=False) activation_date = models.DateField(_('Aktivierungssdatum'), null=True, blank=True) deactivation_date = models.DateField(_('Deaktivierungssdatum'), null=True, blank=True) cancelation_date = models.DateField(_('Kündigüngssdatum'), null=True, blank=True) creation_date = models.DateField(_('Erstellungsdatum'), null=True, blank=True, auto_now_add=True) start_date = models.DateField(_('Gewünschtes Startdatum'), null=False, default=start_of_next_business_year) end_date = models.DateField(_('Gewünschtes Enddatum'), null=True, blank=True) notes = models.TextField( _('Notizen'), max_length=1000, blank=True, help_text=_('Notizen für Administration. Nicht sichtbar für {}'.format( Config.vocabulary('member')))) _future_members = None def __str__(self): namelist = [_(' Einheiten {0}').format(self.size)] namelist.extend(extra.type.name for extra in self.extra_subscriptions.all()) return _('Abo ({1}) {0}').format(' + '.join(namelist), self.id) def __repr__(self): return _('Abo ({})').format(self.id) @property def overview(self): namelist = [_(' Einheiten {0}').format(self.size)] namelist.extend(extra.type.name for extra in self.extra_subscriptions.all()) return '%s' % (' + '.join(namelist)) @property def size(self): sizes = {} for type in self.types.all(): sizes[type.size.product.name] = type.size.units + sizes.get( type.size.product.name, 0) return ', '.join( [key + ':' + str(value) for key, value in sizes.items()]) @property def types_changed(self): return sorted(list(self.types.all())) != sorted( list(self.future_types.all())) def recipients_names(self): members = self.recipients return ', '.join(str(member) for member in members) recipients_names.short_description = '{}-BezieherInnen'.format( Config.vocabulary('subscription')) def other_recipients(self): return self.recipients.exclude(email=self.primary_member.email) def other_recipients_names(self): members = self.other_recipients() return ', '.join(str(member) for member in members) @property def recipients(self): return self.recipients_all.filter(inactive=False) @property def recipients_all(self): return self.recipients_all_for_state(self.state) def recipients_all_for_state(self, state): if state == 'waiting': return self.members_future.all() elif state == 'inactive': return self.members_old.all() else: return self.members.all() def primary_member_nullsave(self): member = self.primary_member return str(member) if member is not None else '' primary_member_nullsave.short_description = primary_member.verbose_name @property def state(self): if self.active is False and self.deactivation_date is None: return 'waiting' elif self.active is True and self.canceled is False: return 'active' elif self.active is True and self.canceled is True: return 'canceled' elif self.active is False and self.deactivation_date is not None: return 'inactive' @property def extra_subscriptions(self): return self.extra_subscription_set.filter(active=True) @property def future_extra_subscriptions(self): return self.extra_subscription_set.filter( Q(active=False, deactivation_date=None) | Q(active=True, canceled=False)) @property def all_shares(self): return ShareDao.all_shares_subscription(self).count() @property def paid_shares(self): return ShareDao.paid_shares(self).count() @property def share_overflow(self): return self.all_shares - self.required_shares @property def extrasubscriptions_changed(self): current_extrasubscriptions = self.extra_subscriptions.all() future_extrasubscriptions = self.future_extra_subscriptions.all() return set(current_extrasubscriptions) != set( future_extrasubscriptions) def subscription_amount(self, size): return self.calc_subscription_amount(self.types, size) def subscription_amount_future(self, size): return self.calc_subscription_amount(self.future_types, size) @staticmethod def calc_subscription_amount(types, size): return types.filter(size=size).count() def future_amount_by_type(self, type): return len(self.future_types.filter(id=type)) @staticmethod def next_extra_change_date(): month = int(time.strftime('%m')) if month >= 7: next_extra = datetime.date(day=1, month=1, year=timezone.now().today().year + 1) else: next_extra = datetime.date(day=1, month=7, year=timezone.now().today().year) return next_extra @staticmethod def next_size_change_date(): return start_of_next_business_year() @staticmethod def get_size_name(types=[]): size_dict = {} for type in types.all(): size_dict[type.__str__()] = 1 + size_dict.get(type.__str__(), 0) size_names = [ key + ':' + str(value) for key, value in size_dict.items() ] if len(size_names) > 0: return '<br>'.join(size_names) return _('kein/e/n {0}').format(Config.vocabulary('subscription')) @property def required_shares(self): result = 0 for type in self.types.all(): result += type.shares return result @property def required_assignments(self): result = 0 for type in self.types.all(): result += type.required_assignments return result @property def required_core_assignments(self): result = 0 for type in self.types.all(): result += type.required_core_assignments return result @property def price(self): result = 0 for type in self.types.all(): result += type.price return result @property def size_name(self): return Subscription.get_size_name(types=self.types) @property def future_size_name(self): return Subscription.get_size_name(types=self.future_types) def extra_subscription_amount(self, extra_sub_type): return self.extra_subscriptions.filter(type=extra_sub_type).count() def clean(self): check_sub_consistency(self) @notifiable class Meta: verbose_name = Config.vocabulary('subscription') verbose_name_plural = Config.vocabulary('subscription_pl') permissions = (('can_filter_subscriptions', _('Benutzer kann {0} filtern').format( Config.vocabulary('subscription'))), )
def member_created(member): EmailSender.get_sender( organisation_subject( _('Neue/r/s {}').format(Config.vocabulary('member_type'))), get_email_content('a_member_created', base_dict(locals())), bcc=get_emails_by_permission('notified_on_member_creation')).send()
class Meta: verbose_name = Config.vocabulary('subscription') verbose_name_plural = Config.vocabulary('subscription_pl') permissions = (('can_filter_subscriptions', _('Benutzer kann {0} filtern').format( Config.vocabulary('subscription'))), )
def __init__(self, model, admin_site): super().__init__(model, admin_site) if not Config.enable_shares(): self.exclude.append('shares')
def vocabulary(key): return Config.vocabulary(key)
def get_submit_button(): return Submit('submit', _('{} hinzufügen').format( Config.vocabulary('co_member')), css_class='btn-success')
def create(cls, sender, instance, created, **kwds): if created and Config.billing(): bill_share(instance)
def button_next_text(self): return _('Keine weiteren {} hinzufügen').format( Config.vocabulary('co_member_pl'))
class Job(PolymorphicModel): slots = models.PositiveIntegerField(_('Plaetze')) time = models.DateTimeField() multiplier = models.PositiveIntegerField(_('{0}) vielfaches').format( Config.vocabulary('assignment')), default=1) pinned = models.BooleanField(default=False) reminder_sent = models.BooleanField(_('Reminder verschickt'), default=False) canceled = models.BooleanField(_('abgesagt'), default=False) old_canceled = None old_time = None @property def type(self): raise NotImplementedError def __str__(self): return _('Job #%s') % (self.id) def weekday_name(self): day = self.time.isoweekday() return weekday_short(day, 2) def time_stamp(self): return int(time.mktime(self.time.timetuple()) * 1000) def free_slots(self): if not (self.slots is None): return self.slots - self.occupied_places() else: return 0 def end_time(self): return self.time + timezone.timedelta(hours=self.type.duration) def start_time(self): return self.time def occupied_places(self): return self.assignment_set.count() def get_status_percentage(self): assignments = AssignmentDao.assignments_for_job(self.id) if self.slots < 1: return get_status_image(100) return get_status_image(assignments.count() * 100 / self.slots) def is_core(self): return self.type.activityarea.core def extras(self): extras_result = [] for extra in self.type.job_extras_set.all(): if extra.empty(self.assignment_set.all()): extras_result.append(extra.extra_type.display_empty) else: extras_result.append(extra.extra_type.display_full) return ' '.join(extras_result) def empty_per_job_extras(self): extras_result = [] for extra in self.type.job_extras_set.filter(per_member=False): if extra.empty(self.assignment_set.all()): extras_result.append(extra) return extras_result def full_per_job_extras(self): extras_result = [] for extra in self.type.job_extras_set.filter(per_member=False): if not extra.empty(self.assignment_set.all()): extras_result.append(extra) return extras_result def per_member_extras(self): return self.type.job_extras_set.filter(per_member=True) def clean(self): if self.old_canceled != self.canceled and self.old_canceled is True: raise ValidationError( _('Abgesagte jobs koennen nicht wieder aktiviert werden'), code='invalid') @classmethod def pre_save(cls, sender, instance, **kwds): if instance.old_canceled != instance.canceled and instance.old_canceled is False: assignments = AssignmentDao.assignments_for_job(instance.id) emails = set() for assignment in assignments: emails.add(assignment.member.email) instance.slots = 0 if len(emails) > 0: send_job_canceled(emails, instance) if instance.old_time != instance.time: assignments = AssignmentDao.assignments_for_job(instance.id) emails = set() for assignment in assignments: emails.add(assignment.member.email) if len(emails) > 0: send_job_time_changed(emails, instance) @classmethod def post_init(cls, sender, instance, **kwds): instance.old_time = instance.time instance.old_canceled = instance.canceled if instance.canceled: assignments = AssignmentDao.assignments_for_job(instance.id) assignments.delete() class Meta: verbose_name = _('AbstractJob') verbose_name_plural = _('AbstractJobs')
def get_promoted_jobs(): return RecuringJob.objects.filter( type__name__in=Config.promoted_job_types(), time__gte=timezone.now()).order_by( 'time')[:Config.promomted_jobs_amount()]
class Meta: verbose_name = Config.vocabulary('assignment') verbose_name_plural = Config.vocabulary('assignment_pl')
def send_fund_confirmation_mail(fund, password=None): plaintext = get_template(CrowdfundingConfig.emails('fund_confirmation_mail')) content = plaintext.render(base_dict(locals())) EmailSender.get_sender( Config.organisation_name() + ' - Beitragsbestätigung', content).send_to(fund.funder.email)
from juntagrico.admins import BaseAdmin from juntagrico.config import Config class ExtraSubscriptionTypeAdmin(BaseAdmin): exclude = [] if not Config.enable_shares(): ExtraSubscriptionTypeAdmin.exclude.append('shares')
def job(request, job_id): ''' Details for a job ''' member = request.user.member job = get_object_or_404(Job, id=int(job_id)) slotrange = list(range(0, job.slots)) allowed_additional_participants = list(range(1, job.free_slots + 1)) job_fully_booked = len(allowed_additional_participants) == 0 job_is_in_past = job.end_time() < timezone.now() job_is_running = job.start_time() < timezone.now() job_canceled = job.canceled can_subscribe = job.infinite_slots or not ( job_fully_booked or job_is_in_past or job_is_running or job_canceled) renderdict = get_menu_dict(request) if request.method == 'POST' and can_subscribe and 0 < int( request.POST.get('jobs')) <= job.free_slots: num = int(request.POST.get('jobs')) # adding participants amount = 1 if Config.assignment_unit() == 'ENTITY': amount = job.multiplier elif Config.assignment_unit() == 'HOURS': amount = job.multiplier * job.duration for i in range(num): assignment = Assignment.objects.create(member=member, job=job, amount=amount) for extra in job.type.job_extras_set.all(): if request.POST.get('extra' + str(extra.extra_type.id)) == str( extra.extra_type.id): assignment.job_extras.add(extra) assignment.save() membernotification.job_signup(member.email, job) # redirect to same page such that refresh in the browser or back # button does not trigger a resubmission of the form return redirect('job', job_id=job_id) elif request.method == 'POST': renderdict['messages'].extend(error_message(request)) all_participants = MemberDao.members_by_job(job) number_of_participants = len(all_participants) unique_participants = all_participants.annotate( assignment_for_job=Count('id')).distinct() participants_summary = [] emails = [] for participant in unique_participants: name = '{} {}'.format(participant.first_name, participant.last_name) if participant.assignment_for_job == 2: name += _(' (mit einer weiteren Person)') elif participant.assignment_for_job > 2: name += _(' (mit {} weiteren Personen)').format( participant.assignment_for_job - 1) contact_url = reverse('contact-member', args=[participant.id]) extras = [] for assignment in AssignmentDao.assignments_for_job_and_member( job.id, participant): for extra in assignment.job_extras.all(): extras.append(extra.extra_type.display_full) reachable = participant.reachable_by_email is True or request.user.is_staff or \ job.type.activityarea.coordinator == participant participants_summary.append( (name, contact_url, reachable, ' '.join(extras))) emails.append(participant.email) renderdict['messages'].extend(job_messages(request, job)) renderdict.update({ 'can_contact': request.user.has_perm('juntagrico.can_send_mails') or (job.type.activityarea.coordinator == member and request.user.has_perm('juntagrico.is_area_admin')), 'emails': '\n'.join(emails), 'number_of_participants': number_of_participants, 'participants_summary': participants_summary, 'job': job, 'slotrange': slotrange, 'allowed_additional_participants': allowed_additional_participants, 'can_subscribe': can_subscribe, 'edit_url': get_job_admin_url(request, job) }) return render(request, 'job.html', renderdict)
def contact_admin_link(text): return mark_safe( escape(text).format('<a href="mailto:{0}">{0}</a>'.format( Config.info_email())))
def start_of_specific_business_year(refdate): day = Config.business_year_start()['day'] month = Config.business_year_start()['month'] return calculate_last_offset(day, month, refdate)