class Meetings(dd.Table): """Base table for all Meetings. """ required_roles = dd.login_required((OfficeUser,)) model = 'meetings.Meeting' detail_layout = MeetingDetail() insert_layout = dd.InsertLayout("""name ref room start_date """, window_size = (40, 10,)) column_names = "start_date name ref site room workflow_buttons *" # order_by = ['start_date'] # order_by = 'line__name room__name start_date'.split() # order_by = ['name'] order_by = ['-start_date', '-start_time'] auto_fit_column_widths = True parameters = mixins.ObservedDateRange( user=dd.ForeignKey( settings.SITE.user_model, blank=True, null=True), show_active=dd.YesNo.field( _("Active"), blank=True, help_text=_("Whether to show rows in some active state")), state=MeetingStates.field(blank=True), member=dd.ForeignKey(dd.resolve_model('contacts.Partner'), verbose_name=_("Member"), blank=True, help_text=_("Show rows that this person is a member")), ) params_layout = """user room state show_active member start_date end_date """ # simple_parameters = 'line teacher state user'.split() @classmethod def get_request_queryset(self, ar, **kwargs): # dd.logger.info("20160223 %s", self) qs = super(Meetings, self).get_request_queryset(ar, **kwargs) if isinstance(qs, list): return qs pv = ar.param_values qs = self.model.add_param_filter( qs, show_active=pv.show_active) if pv.state: qs = qs.filter(state=pv.state) if pv.member: sqs = rt.models.stars.Star.for_model('meetings.Meeting', user=pv.member) stared_ticket_ids = sqs.values_list('owner_id') qs = qs.filter(pk__in=stared_ticket_ids) if pv.start_date: # dd.logger.info("20160512 start_date is %r", pv.start_date) qs = PeriodEvents.started.add_filter(qs, pv) # qs = qs.filter(start_date__gte=pv.start_date) if pv.end_date: qs = PeriodEvents.ended.add_filter(qs, pv) # qs = qs.filter(end_date__lte=pv.end_date) # dd.logger.info("20160512 %s", qs.query) return qs
class Memo(dd.Model): owner_type = dd.ForeignKey(ContentType, blank=True, null=True) owner_id = models.PositiveIntegerField(blank=True, null=True) owner = dd.GenericForeignKey('owner_type', 'owner_id') text = models.CharField(max_length=200)
class Days(dd.VirtualTable): # every row is a Day instance. Note that Day can be overridden. # required_roles = dd.login_required((OfficeUser, OfficeOperator)) required_roles = dd.login_required(OfficeUser) column_names = "detail_link *" parameters = ObservedDateRange( user=dd.ForeignKey('users.User', null=True, blank=True)) model = 'calview.Day' editable = False navigation_mode = "day" # or "week" or "month" abstract = True @classmethod def get_navinfo(cls, ar, day): assert isinstance(day, Day) day.navigation_mode = ar.actor.navigation_mode # so that str() gives the right formar ni = dict(recno=day.pk, message=str(day)) if cls.navigation_mode == "month": ni.update(next=date2pk(day.skipmonth(1))) ni.update(prev=date2pk(day.skipmonth(-1))) ni.update(first=day.pk - 365) ni.update(last=day.pk + 365) elif cls.navigation_mode == "week": ni.update(next=day.pk + 7) ni.update(prev=day.pk - 7) ni.update(first=date2pk(day.skipmonth(-1))) ni.update(last=date2pk(day.skipmonth(1))) elif cls.navigation_mode == "day": ni.update(next=day.pk + 1) ni.update(prev=day.pk - 1) ni.update(first=date2pk(day.skipmonth(-1))) ni.update(last=date2pk(day.skipmonth(1))) else: raise Exception("Invalid navigation_mode {}".format( cls.navigation_mode)) return ni @dd.virtualfield(models.IntegerField(_("Day number"))) def day_number(cls, obj, ar): return obj.pk @classmethod def get_pk_field(cls): # return pk_field # return PK_FIELD # return cls.get_data_elem('day_number') return cls.day_number.return_type @classmethod def get_row_by_pk(cls, ar, pk): """ pk is the offset from beginning_of_time """ #if pk is None: # pk = "0" # return dd.today(int(pk)) # if ar is None: # return cls.model(int(pk)) # return cls.model(int(pk), ar.actor.navigation_mode) return cls.model(int(pk), ar, cls.navigation_mode) @classmethod def get_request_queryset(cls, ar, **filter): home = cls.get_row_by_pk(ar, 0) ni = cls.get_navinfo(ar, home) pv = ar.param_values date = pv.start_date or dd.today(ni['first']) last = pv.end_date or dd.today(ni['last']) if cls.navigation_mode == "day": step = lambda x: x + timedelta(days=1) elif cls.navigation_mode == "week": step = lambda x: x + timedelta(days=7) else: step = lambda x: DurationUnits.months.add_duration(x, 1) while date <= last: yield cls.get_row_by_pk(ar, date2pk(date)) date = step(date)
def setup_parameters(cls, fields): fields.update( place=dd.ForeignKey('countries.Place', blank=True, null=True)) super(Address, cls).setup_parameters(fields)
class Coaching(UserAuthored, mixins.DatePeriod, dd.ImportedFields, ChangeObservable): """A Coaching ("Begleitung" in German and "intervention" in French) is when a given client is being coached by a given user during a given period. For example in :ref:`welfare` that used is a social assistant. """ class Meta: app_label = 'coachings' verbose_name = _("Coaching") verbose_name_plural = _("Coachings") # user = models.ForeignKey( # settings.SITE.user_model, # verbose_name=_("Coach"), # related_name="%(app_label)s_%(class)s_set_by_user", # ) allow_cascaded_delete = ['client'] workflow_state_field = 'state' client = dd.ForeignKey(client_model, related_name="coachings_by_client") type = dd.ForeignKey('coachings.CoachingType', blank=True, null=True) primary = models.BooleanField( _("Primary"), default=False, help_text=_("""There's at most one primary coach per client. \ Enabling this field will automatically make the other \ coachings non-primary.""")) ending = models.ForeignKey('coachings.CoachingEnding', related_name="%(app_label)s_%(class)s_set", blank=True, null=True) @classmethod def on_analyze(cls, site): super(Coaching, cls).on_analyze(site) cls.declare_imported_fields('''client user primary end_date''') @dd.chooser() def ending_choices(cls, type): qs = CoachingEnding.objects.filter(Q(type__isnull=True) | Q(type=type)) return qs.order_by("seqno") def disabled_fields(self, ar): rv = super(Coaching, self).disabled_fields(ar) if settings.SITE.is_imported_partner(self.client): if self.primary: return self._imported_fields return set(['primary']) return rv def on_create(self, ar): """ Default value for the `user` field is the requesting user. """ if self.user_id is None: u = ar.get_user() if u is not None: self.user = u super(Coaching, self).on_create(ar) def disable_delete(self, ar=None): if ar is not None and settings.SITE.is_imported_partner(self.client): if self.primary: return _( "Cannot delete companies and persons imported from TIM") return super(Coaching, self).disable_delete(ar) def before_ui_save(self, ar, **kw): #~ logger.info("20121011 before_ui_save %s",self) super(Coaching, self).before_ui_save(ar, **kw) if not self.type: self.type = ar.get_user().coaching_type if not self.start_date: self.start_date = settings.SITE.today() if self.ending and not self.end_date: self.end_date = settings.SITE.today() #~ def update_system_note(self,note): #~ note.project = self.client def __str__(self): #~ return _("Coaching of %(client)s by %(user)s") % dict(client=self.client,user=self.user) #~ return self.user.username+' / '+self.client.first_name+' '+self.client.last_name[0] cl = self.client if cl.first_name: return self.user.username + ' / ' + cl.last_name + ' ' + cl.first_name[ 0] return self.user.username + ' / ' + cl.last_name def after_ui_save(self, ar, cw): super(Coaching, self).after_ui_save(ar, cw) if self.primary: for c in self.client.coachings_by_client.exclude(id=self.id): if c.primary: c.primary = False c.save() ar.set_response(refresh_all=True) #~ return kw #~ def get_row_permission(self,user,state,ba): #~ """ #~ """ #~ logger.info("20121011 get_row_permission %s %s",self,ba) #~ if isinstance(ba.action,actions.SubmitInsert): #~ if not user.coaching_type: #~ return False #~ return super(Coaching,self).get_row_permission(user,state,ba) def full_clean(self, *args, **kw): if not self.start_date and not self.end_date: self.start_date = settings.SITE.today() if not self.type and self.user: self.type = self.user.coaching_type super(Coaching, self).full_clean(*args, **kw) #~ def save(self,*args,**kw): #~ super(Coaching,self).save(*args,**kw) def summary_row(self, ar, **kw): return [ar.href_to(self.client), " (%s)" % self.state.text] # def get_related_project(self): # return self.client def get_change_owner(self, ar): return self.client def get_change_observers(self): return self.client.get_change_observers()
sender.plugins.memo.parser.register_django_model( 'order', Order, cmd=cmd, # title=lambda obj: obj.name ) # class OrderItem(ProductDocItem, SequencedVoucherItem): class OrderItem(SequencedVoucherItem): class Meta: app_label = 'orders' abstract = dd.is_abstract_model(__name__, 'OrderItem') verbose_name = _("Order item") verbose_name_plural = _("Order items") allow_cascaded_delete = 'voucher' voucher = dd.ForeignKey('orders.Order', related_name='items') # title = models.CharField(_("Heading"), max_length=200, blank=True) product = dd.ForeignKey('products.Product', blank=True, null=True) qty = dd.QuantityField(_("Quantity"), blank=True, null=True) # unit_price = dd.PriceField(_("Unit price"), blank=True, null=True) remark = models.CharField(_("Remark"), max_length=200, blank=True) dd.inject_field('ledger.Journal', 'room', dd.ForeignKey('cal.Room', blank=True, null=True)) from .ui import *
class Order(Certifiable, RegistrableVoucher, RecurrenceSet, EventGenerator, Duplicable, ProjectRelated): class Meta: app_label = 'orders' abstract = dd.is_abstract_model(__name__, 'Order') verbose_name = _("Order") verbose_name_plural = _('Orders') state = OrderStates.field(default='draft') # order_area = OrderAreas.field(default='default') # client = dd.ForeignKey( # "presto.Client", # related_name="%(app_label)s_%(class)s_set_by_client", # blank=True, null=True) # invoice_recipient = dd.ForeignKey('contacts.Partner', verbose_name=_("Invoice recipient"), related_name='orders_by_recipient', blank=True, null=True) subject = models.CharField(_("Our reference"), max_length=200, blank=True) # site_field_name = 'room' # line = dd.ForeignKey('orders.Line') # event_type = dd.ForeignKey('cal.EventType', null=True, blank=True) # room = dd.ForeignKey('cal.Room', blank=True) description = dd.TextField(_("Description"), blank=True) remark = models.TextField(_("Remark"), blank=True) # entry_date = models.DateField( # verbose_name=_("Entry date")) max_date = models.DateField(blank=True, null=True, verbose_name=_("Generate events until")) # order_state = OrderStates.field(default='draft') # quick_search_fields = 'name' # max_places = models.PositiveIntegerField( # pgettext("in a order", "Available places"), # help_text=("Maximum number of participants"), # blank=True, null=True) # # name = models.CharField(_("Designation"), max_length=100, blank=True) # enrolments_until = models.DateField( # _("Enrolments until"), blank=True, null=True) # # print_presence_sheet = PrintPresenceSheet(show_in_bbar=False) # print_presence_sheet_html = PrintPresenceSheet( # show_in_bbar=False, # build_method='weasy2html', # label=format_lazy(u"{}{}",_("Presence sheet"), _(" (HTML)"))) def full_clean(self, *args, **kwargs): if self.entry_date is None: self.entry_date = dd.today() super(Order, self).full_clean(*args, **kwargs) # @dd.displayfield(_("Print")) # def print_actions(self, ar): # if ar is None: # return '' # elems = [] # elems.append(ar.instance_action_button( # self.print_presence_sheet)) # elems.append(ar.instance_action_button( # self.print_presence_sheet_html)) # return E.p(*join_elems(elems, sep=", ")) # def on_duplicate(self, ar, master): # self.state = OrderStates.draft # super(Order, self).on_duplicate(ar, master) # def update_cal_until(self): return self.max_date def get_partner(self): if self.invoice_recipient is not None: return self.invoice_recipient if self.project is not None: return self.project.get_partner() def get_wanted_movements(self): return [] # @classmethod # def add_param_filter( # cls, qs, lookup_prefix='', show_exposed=None, **kwargs): # qs = super(Order, cls).add_param_filter(qs, **kwargs) # exposed_states = OrderStates.filter(is_exposed=True) # fkw = dict() # fkw[lookup_prefix + 'state__in'] = exposed_states # if show_exposed == dd.YesNo.no: # qs = qs.exclude(**fkw) # elif show_exposed == dd.YesNo.yes: # qs = qs.filter(**fkw) # return qs @classmethod def get_registrable_fields(cls, site): for f in super(Order, cls).get_registrable_fields(site): yield f yield 'name' def update_cal_rset(self): return self def update_cal_from(self, ar): """Note: if recurrency is weekly or per_weekday, actual start may be later than self.start_date """ # if self.state in (OrderStates.draft, OrderStates.cancelled): # if self.state == OrderStates.cancelled: # ar.info("No start date because state is %s", self.state) # return None return self.start_date def update_cal_event_type(self): if self.journal.room_id: return self.journal.room.event_type def update_cal_summary(self, et, i): if self.every_unit == Recurrencies.once: return str(self) return "%s %s" % (dd.babelattr(et, 'event_label'), i) # def get_events_user(self): # """The user of generated events is not the order manager (author) but # the teacher. # # """ # if self.teacher: # return self.teacher.get_as_user() or self.user # return self.user def suggest_cal_guests(self, event): """Look up enrolments of this order and suggest them as guests.""" # logger.info("20140314 suggest_guests") Enrolment = rt.models.orders.Enrolment qs = Enrolment.objects.filter(order=self).order_by('id') for obj in qs: # if obj.is_guest_for(event): g = obj.make_guest_for(event) if g is not None: yield g def before_auto_event_save(self, event): """ Set room and start_time/end_time for automatic events. """ assert not settings.SITE.loading_from_dump assert event.owner == self event.order = self event.room = self.journal.room event.start_time = self.start_time event.end_time = self.end_time super(Order, self).before_auto_event_save(event) @dd.displayfield(_("Calendar entries")) def events_text(self, ar=None): return ', '.join([ day_and_month(e.start_date) for e in self.events_by_order().order_by('start_date') ]) def events_by_order(self, **kwargs): ct = rt.models.contenttypes.ContentType.objects.get_for_model( self.__class__) kwargs.update(owner_type=ct, owner_id=self.id) return rt.models.cal.Event.objects.filter(**kwargs) @dd.requestfield(_("Enrolments")) def enrolments(self, ar): return self.get_enrolments() def get_enrolments(self, **pv): return rt.models.orders.EnrolmentsByOrder.request(self, param_values=pv)
class ChatMessage(UserAuthored, Created, Previewable): class Meta(object): app_label = 'chat' verbose_name = _("Chat message") verbose_name_plural = _("Chat messages") ordering = ['created', 'id'] # message_type = MessageTypes.field(default="change") seen = models.DateTimeField(_("seen"), null=True, editable=False) sent = models.DateTimeField(_("sent"), null=True, editable=False) group = dd.ForeignKey('chat.ChatGroup', blank=True, null=True, verbose_name=_(u'Group'), related_name="messages") hash = dd.CharField(_("Hash"), max_length=25, null=True, blank=True) # body = dd.RichTextField(_("Body"), editable=False, format='html') def __str__(self): return "{}: {}".format(self.user, self.body) # return _("About {0}").format(self.owner) # return self.message # return _("Notify {0} about change on {1}").format( # self.user, self.owner) def send_global_message(self): # message = { # "id": self.pk, # # "subject": str(self.subject), # "user": self.user, # "body": html2text(self.body), # "created": self.created.strftime("%a %d %b %Y %H:%M"), # } logger.info("Sending Message %s:#%s" % (self.user, self.pk)) send_global_chat(self) # self.sent = timezone.now() # self.save() @classmethod def markAsSeen(Cls, data): group_id = data['body'][0] # msg_ids = [chat[3] for chat in data['body'][1]] ChatProps.objects.filter(chat__group__pk=group_id, user=data['user'], seen__isnull=True).update(seen=timezone.now()) @classmethod def onRecive(Cls, data): print(data) args = dict(user=data['user'], body=data['body']['body'], group_id=data['body']['group_id'], hash=data['body']['hash']) newMsg = Cls(**args) newMsg.full_clean() newMsg.save() newMsg.after_ui_create() newMsg.send_global_message() def after_ui_create(self, *args): for sub in self.group.ChatGroupsMembers.all().select_related("user"): props = ChatProps(chat=self, user=sub.user) props.full_clean() props.save() # super(ChatMessage, self).after_ui_save() @dd.action(_("ChatsMsg")) def getChats(self, ar): # return ar.success(rows=[]) # doto, have work. last_ten_seen = ChatProps.objects.filter(seen__isnull=False).order_by( '-created').select_related("chat")[:10] unseen = ChatProps.objects.filter( user=ar.get_user(), chat=self, seen__isnull=True).order_by('-created').select_related("chat") last_ten_in_ascending_order = reversed(last_ten_seen) unseen_in_ascending_order = reversed(unseen) rows = [[(c.chat.user.username, ar.parse_memo(c.chat.body), c.chat.created, c.seen, c.chat.pk, c.chat.user.id) for c in last_ten_in_ascending_order], [(c.chat.user.username, ar.parse_memo(c.chat.body), c.chat.created, c.seen, c.chat.pk, c.chat.user.id) for c in unseen_in_ascending_order]] return ar.success(rows=rows)
class ExpectedMovements(dd.VirtualTable): """ A virtual table of :class:`DueMovement` rows, showing all "expected" "movements (payments)". Subclassed by :class:`lino_cosi.lib.finan.models.SuggestionsByVoucher`. """ row_height = 4 required_roles = dd.required(AccountingReader) label = _("Debts") icon_name = 'book_link' #~ column_names = 'match due_date debts payments balance' column_names = 'due_date:15 balance debts payments' auto_fit_column_widths = True # variable_row_height = True parameters = dd.ParameterPanel( date_until=models.DateField(_("Date until"), blank=True, null=True), trade_type=TradeTypes.field(blank=True), from_journal=dd.ForeignKey('ledger.Journal', blank=True), for_journal=dd.ForeignKey('ledger.Journal', blank=True, verbose_name=_("Clearable by")), account=dd.ForeignKey('accounts.Account', blank=True), partner=dd.ForeignKey('contacts.Partner', blank=True), project=dd.ForeignKey(dd.plugins.ledger.project_model, blank=True), ) params_layout = "trade_type date_until from_journal " \ "for_journal project partner account" @classmethod def get_dc(cls, ar=None): return DEBIT @classmethod def get_data_rows(cls, ar, **flt): #~ if ar.param_values.journal: #~ pass pv = ar.param_values # if pv is None: # raise Exception("No pv in %s" % ar) if pv.trade_type: flt.update(account=pv.trade_type.get_partner_account()) if pv.partner: flt.update(partner=pv.partner) if pv.account: flt.update(account=pv.account) if pv.project: flt.update(project=pv.project) if pv.date_until is not None: flt.update(value_date__lte=pv.date_until) if pv.for_journal is not None: accounts = rt.modules.accounts.Account.objects.filter( matchrule__journal=pv.for_journal).distinct() flt.update(account__in=accounts) if pv.from_journal is not None: flt.update(voucher__journal=pv.from_journal) return get_due_movements(cls.get_dc(ar), **flt) @classmethod def get_pk_field(self): return rt.modules.ledger.Movement._meta.pk @classmethod def get_row_by_pk(cls, ar, pk): # for i in ar.data_iterator: # if i.id == pk: # return i # raise Exception("Not found: %s in %s" % (pk, ar)) mvt = rt.modules.ledger.Movement.objects.get(pk=pk) dm = DueMovement(cls.get_dc(ar), mvt) dm.collect_all() return dm @dd.displayfield(_("Info")) def info(self, row, ar): elems = [] if row.project: elems.append(ar.obj2html(row.project)) if row.partner: elems.append(ar.obj2html(row.partner)) # elems.append(row.partner.address) if row.bank_account: elems.append(ar.obj2html(row.bank_account)) if row.account: elems.append(ar.obj2html(row.account)) # return E.span(*join_elems(elems, ' / ')) return E.span(*join_elems(elems, E.br)) # return E.span(*elems) @dd.displayfield(_("Match")) def match(self, row, ar): return row.match @dd.virtualfield( models.DateField( _("Due date"), help_text=_("Due date of the eldest debt in this match group"))) def due_date(self, row, ar): return row.due_date @dd.displayfield(_("Debts"), help_text=_("List of invoices in this match group")) def debts(self, row, ar): return E.span(*join_elems([ # E.p(...) until 20150128 ar.obj2html(i.voucher.get_mti_leaf()) for i in row.debts ])) @dd.displayfield(_("Payments"), help_text=_("List of payments in this match group")) def payments(self, row, ar): return E.span(*join_elems([ # E.p(...) until 20150128 ar.obj2html(i.voucher.get_mti_leaf()) for i in row.payments ])) @dd.virtualfield(dd.PriceField(_("Balance"))) def balance(self, row, ar): return row.balance @dd.virtualfield(dd.ForeignKey('contacts.Partner')) def partner(self, row, ar): return row.partner @dd.virtualfield(dd.ForeignKey(dd.plugins.ledger.project_model)) def project(self, row, ar): return row.project @dd.virtualfield(dd.ForeignKey('accounts.Account')) def account(self, row, ar): return row.account @dd.virtualfield( dd.ForeignKey('sepa.Account', verbose_name=_("Bank account"))) def bank_account(self, row, ar): return row.bank_account
class Controllable(dd.Model): """Mixin for models that are "controllable" by another database object. Defines three fields :attr:`owned_type`, :attr:`owned_id` and :attr:`owned`. And a class attribute :attr:`owner_label`. For example in :mod:`lino.modlibs.cal`, the owner of a Task or Event is some other database object that caused the task's or event's creation. Or in :mod:`lino.modlib.sales` and :mod:`lino.modlib.purchases`, an invoice may cause one or several Tasks to be automatically generated when a certain payment mode is specified. Controllable objects are "governed" or "controlled" by their controller (stored in a field called :attr:`owner`): If the controller gets modified, it may decide to delete or modify some or all of her controlled objects. Non-automatic tasks always have an empty :attr:`owner` field. Some fields are read-only on an automatic Task because it makes no sense to modify them. .. attribute:: owner .. attribute:: owner_id .. attribute:: owner_type """ # Translators: will also be concatenated with '(type)' '(object)' owner_label = _('Controlled by') """The labels (`verbose_name`) of the fields `owned_type`, `owned_id` and `owned` are derived from this attribute which may be overridden by subclasses. """ controller_is_optional = True """Whether the controller is optional (i.e. whether the :attr:`owner` field may be NULL) """ class Meta: abstract = True owner_type = dd.ForeignKey(ContentType, editable=True, blank=controller_is_optional, null=controller_is_optional, verbose_name=string_concat( owner_label, ' ', _('(type)'))) owner_id = dd.GenericForeignKeyIdField(owner_type, editable=True, blank=controller_is_optional, null=controller_is_optional, verbose_name=string_concat( owner_label, ' ', _('(object)'))) owner = dd.GenericForeignKey('owner_type', 'owner_id', verbose_name=owner_label) def update_owned_instance(self, controllable): """If this (acting as a controller) is itself controlled, forward the call to the controller. """ if self.owner: self.owner.update_owned_instance(controllable) super(Controllable, self).update_owned_instance(controllable) def save(self, *args, **kw): if settings.SITE.loading_from_dump: super(Controllable, self).save(*args, **kw) else: if self.owner: self.owner.update_owned_instance(self) super(Controllable, self).save(*args, **kw) if self.owner: self.owner.after_update_owned_instance(self)
class Address(AddressLocation): """Inherits fields from :class:`lino.modlib.countries.CountryRegionCity` (country, region, city. zip_code) and :class:`lino.modlib.contacts.AddresssLocation` (street, street_no, ...) .. attribute:: partner .. attribute:: address_type .. attribute:: data_source Pointer to :class:`choicelists.DataSources`. Specifies how this information entered into our database. .. attribute:: primary Whether this address is the primary address of its owner. Setting this field will automatically uncheck any previousl primary addresses and update the owner's address fields. """ class Meta: verbose_name = _("Address") verbose_name_plural = _("Addresses") data_source = DataSources.field( editable=False, default=DataSources.manually.as_callable) # address_type = AddressTypes.field(blank=True, null=True) address_type = AddressTypes.field( default=AddressTypes.official.as_callable) partner = dd.ForeignKey( 'contacts.Partner', related_name='addresses_by_partner') remark = dd.CharField(_("Remark"), max_length=50, blank=True) primary = models.BooleanField( _("Primary"), default=False, help_text=_( "Checking this field will automatically uncheck any " "previous primary addresses and update " "the partner's address data fields.")) allow_cascaded_delete = ['partner'] def __unicode__(self): return self.address_location(', ') def after_ui_save(self, ar, cw): super(Address, self).after_ui_save(ar, cw) mi = self.partner if mi is None: return if self.primary: for o in mi.addresses_by_partner.exclude(id=self.id): if o.primary: o.primary = False o.save() ar.set_response(refresh_all=True) mi.sync_primary_address(ar.request) def living_at_text(self): lines = list(self.address_location_lines()) return self.address_type.living_text + ' ' + ', '.join(lines)
class BeIdCardHolder(dd.Model): """Mixin for models which represent an eid card holder. Currently only Belgian eid cards are tested. Concrete subclasses must also inherit from :mod:`lino.mixins.Born`. .. attribute:: national_id The SSIN. It is a *nullable char field* declared *unique*. It is not validated directly because that would cause problems with legacy data where SSINs need manual control. See also :class:`BeIdCardHolderChecker`. .. attribute:: nationality The nationality. This is a pointer to :class:`countries.Country <lino_xl.lib.statbel.countries.models.Country>` which should contain also entries for refugee statuses. Note that the nationality is *not* being read from eID card because it is stored there as a language and gender specific plain text. .. attribute:: image Virtual field which displays the picture. """ class Meta: abstract = True validate_national_id = False """Whether to validate the :attr:`national_id` immediately before saving a record. If this is `False`, the :attr:`national_id` might contain invalid values which would then cause plausibility problems. """ national_id = dd.NullCharField( max_length=200, unique=True, verbose_name=_("National ID") #~ blank=True,verbose_name=_("National ID") # ~ ,validators=[ssin.ssin_validator] # 20121108 ) birth_country = dd.ForeignKey("countries.Country", blank=True, null=True, verbose_name=_("Birth country"), related_name='by_birth_place') birth_place = models.CharField( _("Birth place"), max_length=200, blank=True, #~ null=True ) nationality = dd.ForeignKey('countries.Country', blank=True, null=True, related_name='by_nationality', verbose_name=_("Nationality")) card_number = models.CharField( max_length=20, blank=True, # null=True, verbose_name=_("eID card number")) card_valid_from = models.DateField(blank=True, null=True, verbose_name=_("ID card valid from")) card_valid_until = models.DateField(blank=True, null=True, verbose_name=_("until")) card_type = BeIdCardTypes.field(blank=True) card_issuer = models.CharField( max_length=50, blank=True, # null=True, verbose_name=_("eID card issuer")) "The administration who issued this ID card." read_beid = BeIdReadCardAction() find_by_beid = FindByBeIdAction() noble_condition = models.CharField( max_length=50, blank=True, # null=True, verbose_name=_("noble condition"), help_text=_("The eventual noble condition of this person.")) beid_readonly_fields = set( 'noble_condition card_valid_from card_valid_until \ card_issuer card_number card_type'.split()) def disabled_fields(self, ar): rv = super(BeIdCardHolder, self).disabled_fields(ar) if not ar.get_user().profile.has_required_roles([dd.SiteStaff]): rv |= self.beid_readonly_fields #~ logger.info("20130808 beid %s", rv) return rv def has_valid_card_data(self, today=None): if not self.card_number: return False if self.card_valid_until < (today or dd.today()): return False return True def full_clean(self): if self.validate_national_id and self.national_id: self.national_id = ssin.parse_ssin(self.national_id) super(BeIdCardHolder, self).full_clean() @dd.displayfield(_("eID card"), default='<br/><br/><br/><br/>') def eid_info(self, ar): "Display some information about the eID card." attrs = dict(class_="lino-info") if ar is None: return E.div(**attrs) must_read = False elems = [] if self.card_number: elems += [ "%s %s (%s)" % (ugettext("Card no."), self.card_number, self.card_type) ] if self.card_issuer: elems.append(", %s %s" % (ugettext("issued by"), self.card_issuer)) #~ card_issuer = _("issued by"), if self.card_valid_until is not None: valid = ", %s %s %s %s" % ( ugettext("valid from"), dd.dtos(self.card_valid_from), ugettext("until"), dd.dtos(self.card_valid_until)) if self.card_valid_until < dd.today(): must_read = True elems.append(E.b(valid)) elems.append(E.br()) else: elems.append(valid) else: must_read = True else: must_read = True if must_read: msg = _("Must read eID card!") if dd.plugins.beid: elems.append( ar.instance_action_button(self.read_beid, msg, icon_name=None)) else: elems.append(msg) # same red as in lino.css for .x-grid3-row-red td # ~ attrs.update(style="background-color:#FA7F7F; padding:3pt;") attrs.update(class_="lino-info-red") return E.div(*elems, **attrs) def get_beid_diffs(obj, attrs): """Return two lists, one with the objects to save, and another with text lines to build a confirmation message explaining which changes are going to be applied after confirmation. The default implemantion is for the simple case where the holder is also a contacts.AddressLocation and the address is within the same database row. """ raise Exception("not tested") diffs = [] objects = [] # model = holder_model() model = obj.__class__ # the holder for fldname, new in attrs.items(): fld = get_field(model, fldname) old = getattr(obj, fldname) if old != new: diffs.append("%s : %s -> %s" % (unicode( fld.verbose_name), dd.obj2str(old), dd.obj2str(new))) setattr(obj, fld.name, new) return objects, diffs @dd.htmlbox() def image(self, ar): url = self.get_image_url(ar) return E.a(E.img(src=url, width="100%"), href=url, target="_blank") # s = '<img src="%s" width="100%%"/>' % url # s = '<a href="%s" target="_blank">%s</a>' % (url, s) # return s def get_image_url(self, ar): if self.card_number: parts = get_image_parts(self.card_number) return settings.SITE.build_media_url(*parts) return settings.SITE.build_static_url("contacts.Person.jpg") def get_image_path(self): return get_image_path(self.card_number) def make_demo_picture(self): """Create a demo picture for this card holder. """ if not self.card_number: raise Exception("20150730") src = self.mf(MALE, FEMALE) dst = self.get_image_path() # dst = settings.SITE.cache_dir.child( # 'media', 'beid', self.card_number + '.jpg') if dst.needs_update([src]): logger.info("Create demo picture %s", dst) settings.SITE.makedirs_if_missing(dst.parent) src.copy(dst) else: logger.info("Demo picture %s is up-to-date", dst)
class Coachings(dd.Table): required_roles = dd.login_required(CoachingsStaff) model = 'coachings.Coaching' parameters = mixins.ObservedDateRange( coached_by=dd.ForeignKey( 'users.User', blank=True, null=True, verbose_name=_("Coached by"), help_text="""Nur Begleitungen dieses Benutzers."""), and_coached_by=dd.ForeignKey( 'users.User', blank=True, null=True, verbose_name=_("and by"), help_text="""... und auch Begleitungen dieses Benutzers."""), observed_event=dd.PeriodEvents.field( blank=True, default=dd.PeriodEvents.as_callable('active')), primary_coachings=dd.YesNo.field( _("Primary coachings"), blank=True, help_text="""Accompagnements primaires."""), coaching_type=dd.ForeignKey( 'coachings.CoachingType', blank=True, null=True, help_text="""Nur Begleitungen dieses Dienstes."""), ending=dd.ForeignKey( 'coachings.CoachingEnding', blank=True, null=True, help_text="""Nur Begleitungen mit diesem Beendigungsgrund."""), ) params_layout = """ start_date end_date observed_event coached_by and_coached_by primary_coachings coaching_type ending """ params_panel_hidden = True #~ @classmethod #~ def param_defaults(self,ar,**kw): #~ kw = super(Coachings,self).param_defaults(ar,**kw) #~ D = datetime.date #~ kw.update(start_date = D.today()) #~ kw.update(end_date = D.today()) #~ return kw @classmethod def get_request_queryset(self, ar, **filter): qs = super(Coachings, self).get_request_queryset(ar, **filter) pv = ar.param_values coaches = [] for u in (pv.coached_by, pv.and_coached_by): if u is not None: coaches.append(u) if len(coaches): qs = qs.filter(user__in=coaches) ce = pv.observed_event if ce is not None: qs = ce.add_filter(qs, pv) if pv.primary_coachings == dd.YesNo.yes: qs = qs.filter(primary=True) elif pv.primary_coachings == dd.YesNo.no: qs = qs.filter(primary=False) if pv.coaching_type is not None: qs = qs.filter(type=pv.coaching_type) if pv.ending is not None: qs = qs.filter(ending=pv.ending) return qs @classmethod def get_title_tags(self, ar): for t in super(Coachings, self).get_title_tags(ar): yield t pv = ar.param_values if pv.observed_event: yield six.text_type(pv.observed_event) if pv.coached_by: s = six.text_type(self.parameters['coached_by'].verbose_name) + \ ' ' + six.text_type(pv.coached_by) if pv.and_coached_by: s += " %s %s" % (six.text_type(_('and')), pv.and_coached_by) yield s if pv.primary_coachings: yield six.text_type(self.parameters['primary_coachings'].verbose_name) \ + ' ' + six.text_type(pv.primary_coachings) @classmethod def get_create_permission(self, ar): """Reception clerks can see coachings, but cannot modify them nor add new ones. """ if not ar.get_user().user_type.has_required_roles([CoachingsUser]): #if not ar.get_user().user_type.coaching_level: return False return super(Coachings, self).get_create_permission(ar)
class Delivery(dd.Model): client1 = dd.ForeignKey(Client1) product = dd.ForeignKey(Product) designation = models.CharField("Designation",default='give_same_Product_Name', max_length=40, null=True,blank=True) #price = models.DecimalField("Price", decimal_places=2, max_digits=10) price=dd.PriceField(default=0) discount=dd.QuantityField(null=True ,blank=True) #date = models.DateField(auto_now=True) for update auto_now_add for create date = models.DateField(auto_now_add=True) hour = models.TimeField(auto_now_add=True) qty=dd.QuantityField(default='1') total_d=dd.PriceField(default=0) def __str__(self): if self.discount is None: self.total_d=self.total() self.reduce() return "%s x %s = %s EUR" % (self.qty, self.price, self.total_d) else: self.total_d=self.total() self.reduce() return "%s x %s (-%s) = %s EUR" % (self.qty, self.price, self.discount, self.total_d) def total(self): if self.discount is None: return self.qty * self.price else: return self.qty * (1 - self.discount) * self.price def reduce(self): for self.p in Product.objects.all(): #if self.p.product_name =='apple' : #self.p.product_qt = 999 #self.p.save() # collect the product_name # if self.p.product_name == self.product.product_name: # self.designation = self.product.product_name # self.save() if self.p.product_price == self.product.product_price: self.price = self.product.product_price self.save() if self.p.product_name == self.designation: self.p.last_qt= self.qty self.p.save() self.p.rest_qt=self.p.product_qt - self.qty self.p.save() self.p.new_qt=True self.p.save() if self.p.product_name == self.product.product_name: self.designation = self.product.product_name + '+' self.save() if self.p.product_name == self.product.product_name: self.p.product_qt = self.product.rest_qt self.p.save()
class ClientCoachingsChecker(ClientChecker): verbose_name = _("Check coachings") def get_checkdata_problems(self, obj, fix=False): if obj.client_state == ClientStates.coached: if obj.is_obsolete: yield (False, _("Both coached and obsolete.")) if obj.client_state != ClientStates.coached: today = dd.today() period = (today, today) qs = obj.get_coachings(period) if qs.count(): yield (False, _("Not coached, but with active coachings.")) ClientCoachingsChecker.activate() dd.inject_field( 'users.User', 'coaching_type', dd.ForeignKey( 'coachings.CoachingType', blank=True, null=True)) dd.inject_field( 'users.User', 'coaching_supervisor', models.BooleanField( _("Coaching supervisor"), default=False))
class Movements(dd.Table): """ The base table for all tables working on :class:`Movement`. Defines filtering parameters and general behaviour. Subclassed by e.g. :class:`AllMovements`, :class:`MovementsByVoucher`, :class:`MovementsByAccount` and :class:`MovementsByPartner`. See also :class:`lino_cosi.lib.ledger.models.Movement`. """ model = 'ledger.Movement' required_roles = dd.login_required(LedgerUser) column_names = 'value_date voucher_link description \ debit credit match_link cleared *' sum_text_column = 2 editable = False parameters = mixins.ObservedPeriod( year=FiscalYears.field(blank=True), journal_group=JournalGroups.field(blank=True), partner=dd.ForeignKey('contacts.Partner', blank=True, null=True), project=dd.ForeignKey(dd.plugins.ledger.project_model, blank=True, null=True), account=dd.ForeignKey('accounts.Account', blank=True, null=True), journal=JournalRef(blank=True), cleared=dd.YesNo.field(_("Show cleared movements"), blank=True)) params_layout = """ start_date end_date cleared journal_group journal year project partner account""" @classmethod def get_request_queryset(cls, ar): qs = super(Movements, cls).get_request_queryset(ar) pv = ar.param_values if pv.cleared == dd.YesNo.yes: qs = qs.filter(cleared=True) elif pv.cleared == dd.YesNo.no: qs = qs.filter(cleared=False) # if ar.param_values.partner: # qs = qs.filter(partner=ar.param_values.partner) # if ar.param_values.paccount: # qs = qs.filter(account=ar.param_values.paccount) if pv.year: qs = qs.filter(voucher__accounting_period__year=pv.year) if pv.journal_group: qs = qs.filter(voucher__journal__journal_group=pv.journal_group) if pv.journal: qs = qs.filter(voucher__journal=pv.journal) return qs @classmethod def get_sum_text(self, ar, sums): bal = sums['debit'] - sums['credit'] return _("Balance {1} ({0} movements)").format(ar.get_total_count(), bal) @classmethod def get_simple_parameters(cls): p = super(Movements, cls).get_simple_parameters() p.add('partner') p.add('project') # p.add('journal_group') # p.add('year') p.add('account') return p @classmethod def get_title_tags(cls, ar): for t in super(Movements, cls).get_title_tags(ar): yield t pv = ar.param_values if pv.journal is not None: yield pv.journal.ref if pv.journal_group is not None: yield unicode(pv.journal_group) if pv.year is not None: yield unicode(pv.year) if pv.cleared == dd.YesNo.no: yield unicode(_("only uncleared")) elif pv.cleared == dd.YesNo.yes: yield unicode(_("only cleared")) @dd.displayfield(_("Description")) def description(cls, self, ar): if ar is None: return '' elems = [] elems.append(ar.obj2html(self.account)) voucher = self.voucher.get_mti_leaf() if voucher.narration: elems.append(voucher.narration) p = voucher.get_partner() if p is not None: elems.append(ar.obj2html(p)) if self.project: elems.append(ar.obj2html(self.project)) return E.p(*join_elems(elems, " / "))
class Coaching(UserAuthored, mixins.DateRange, dd.ImportedFields, ChangeNotifier): class Meta: app_label = 'coachings' verbose_name = _("Coaching") verbose_name_plural = _("Coachings") abstract = dd.is_abstract_model(__name__, 'Coaching') # user = dd.ForeignKey( # settings.SITE.user_model, # verbose_name=_("Coach"), # related_name="%(app_label)s_%(class)s_set_by_user", # ) allow_cascaded_delete = ['client'] workflow_state_field = 'state' manager_roles_required = dd.login_required(CoachingsStaff) client = dd.ForeignKey(client_model, related_name="coachings_by_client") type = dd.ForeignKey('coachings.CoachingType', blank=True, null=True) primary = models.BooleanField(_("Primary"), default=False) ending = dd.ForeignKey( 'coachings.CoachingEnding', related_name="%(app_label)s_%(class)s_set", blank=True, null=True) @classmethod def on_analyze(cls, site): super(Coaching, cls).on_analyze(site) cls.declare_imported_fields('''client user primary end_date''') @dd.chooser() def ending_choices(cls, type): qs = CoachingEnding.objects.filter( Q(type__isnull=True) | Q(type=type)) return qs.order_by("seqno") # def disabled_fields(self, ar): # rv = super(Coaching, self).disabled_fields(ar) # if settings.SITE.is_imported_partner(self.client): # if self.primary: # rv |= self._imported_fields # rv.add('primary') # return rv def on_create(self, ar): """ Default value for the `user` field is the requesting user. """ if self.user_id is None: u = ar.get_user() if u is not None: self.user = u super(Coaching, self).on_create(ar) def disable_delete(self, ar=None): if ar is not None and settings.SITE.is_imported_partner(self.client): if self.primary: return _("Cannot delete companies and persons imported from TIM") return super(Coaching, self).disable_delete(ar) def before_ui_save(self, ar, cw): #~ logger.info("20121011 before_ui_save %s",self) super(Coaching, self).before_ui_save(ar, cw) if not self.type: self.type = ar.get_user().coaching_type if not self.start_date: self.start_date = settings.SITE.today() if self.ending and not self.end_date: self.end_date = settings.SITE.today() #~ def update_system_note(self,note): #~ note.project = self.client def __str__(self): #~ return _("Coaching of %(client)s by %(user)s") % dict(client=self.client,user=self.user) #~ return self.user.username+' / '+self.client.first_name+' '+self.client.last_name[0] if not self.client_id: return super(Coaching, self).__str__() cl = self.client if self.user_id is None: return "{} {}".format( self.__class__._meta.verbose_name, cl) if cl.first_name: return self.user.username + ' / ' + cl.last_name + ' ' + cl.first_name[0] return self.user.username + ' / ' + cl.last_name def adapt_primary(self): if self.primary: qs = self.client.coachings_by_client.exclude(id=self.id) if dd.plugins.coachings.multiple_primary_coachings: qs = qs.filter(type=self.type) for c in qs: if c.primary: c.primary = False c.save() return True def after_ui_save(self, ar, cw): super(Coaching, self).after_ui_save(ar, cw) if self.adapt_primary(): ar.set_response(refresh_all=True) #~ return kw #~ def get_row_permission(self,user,state,ba): #~ """ #~ """ #~ logger.info("20121011 get_row_permission %s %s",self,ba) #~ if isinstance(ba.action,actions.SubmitInsert): #~ if not user.coaching_type: #~ return False #~ return super(Coaching,self).get_row_permission(user,state,ba) def full_clean(self, *args, **kw): if not self.start_date and not self.end_date: self.start_date = settings.SITE.today() if not self.type and self.user: self.type = self.user.coaching_type super(Coaching, self).full_clean(*args, **kw) #~ def save(self,*args,**kw): #~ super(Coaching,self).save(*args,**kw) def summary_row(self, ar, **kw): return [ar.href_to(self.client), " (%s)" % self.state.text] # def get_related_project(self): # return self.client def get_change_owner(self): return self.client
class Transaction(dd.Model): """A transaction within a bank statement. This data is automaticaly imported by :class:`ImportStatements`. .. attribute:: statement .. attribute:: seqno .. attribute:: booking_date .. attribute:: value_date .. attribute:: transfer_type The actual historic name of the :attr:`txcd`. .. attribute:: txcd The Bank Transaction Code (`<BkTxCd>`) or "transfer type". Actually it is the "proprietary" part of this code. .. attribute:: txcd_issuer The issuer or the :attr:`txcd`. .. attribute:: txcd_text Virtual field with the textual translated description of the :attr:`txcd`. Currently this works only for Belgian codes where :attr:`txcd_issuer` is `"BBA"` as defined in :mod:`lino_cosi.lib.b2c.febelfin`). .. attribute:: remote_account .. attribute:: remote_bic .. attribute:: remote_owner .. attribute:: remote_owner_address .. attribute:: remote_owner_city .. attribute:: remote_owner_postalcode .. attribute:: remote_owner_country_code """ class Meta: app_label = 'b2c' abstract = dd.is_abstract_model(__name__, 'Transaction') verbose_name = _("Transaction") verbose_name_plural = _("Transactions") statement = dd.ForeignKey('b2c.Statement') seqno = models.IntegerField( _('No.'), help_text=_( "The sequence number of this transaction in the statement.")) # unique_import_id = models.CharField(_('Unique import ID'), max_length=128) # movement_number = models.CharField(_("Ref of Mov"), null=False, max_length=32) # movement_date = models.DateField(_('Movement date'), null=True, blank=True) amount = dd.PriceField(_('Amount'), null=True, blank=True) # partner = models.ForeignKey('contacts.Partner', related_name='b2c_movement', null=True) # partner_name = models.CharField(_('Partner name'), max_length=35, blank=True) remote_account = models.CharField(_("IBAN"), blank=True, max_length=64) remote_bic = BICField(verbose_name=_("BIC"), blank=True) # ref = models.CharField(_('Ref'), max_length=35, blank=True) message = models.TextField(_('Message'), blank=True) eref = models.CharField(_('End to end reference'), max_length=128, blank=True) remote_owner = models.CharField(_('Remote owner'), max_length=128, blank=True) remote_owner_address = models.TextField(_('Remote owner adress'), blank=True) remote_owner_city = models.CharField(_('Remote owner city'), max_length=32, blank=True) remote_owner_postalcode = models.CharField(_('Remote owner postal code'), max_length=10, blank=True) remote_owner_country_code = models.CharField( _('Remote owner country code'), max_length=4, blank=True) txcd = models.CharField(_('Transfer type'), max_length=32, blank=True) txcd_issuer = models.CharField(_('TxCd issuer'), max_length=35, blank=True) booking_date = models.DateField(_('Execution date'), null=True, blank=True) value_date = models.DateField(_('Value date'), null=True, blank=True) @dd.displayfield(_("Remote account")) def remote_html(self, ar): elems = [] elems += [self.remote_account, " "] elems += ["(BIC:", self.remote_bic, ")"] elems.append(E.br()) elems += [E.b(self.remote_owner), ", "] elems.append(E.br()) elems += [" / ".join(self.remote_owner_address.splitlines()), ", "] elems += [self.remote_owner_postalcode, " "] elems += [self.remote_owner_city, " "] elems += [self.remote_owner_country_code] return E.div(*elems) @dd.displayfield(_("Message")) def message_html(self, ar): from django.utils.translation import ugettext as _ elems = [] # elems += [_("Date"), dd.fds(self.transaction_date), " "] # elems += [_("Amount"), ' ', E.b(unicode(self.amount)), " "] # self.booking_date elems += self.message # .splitlines() elems.append(E.br()) # elems += [_("ref:"), ': ', self.ref, ' '] elems += [_("eref:"), ': ', self.eref] elems.append(E.br()) elems += [E.b(self.txcd_text), ' '] elems += [_("Value date"), ': ', E.b(dd.fds(self.value_date)), " "] elems += [_("Booking date"), ': ', E.b(dd.fds(self.booking_date)), " "] return E.div(*elems) @dd.displayfield(_("BkTxCd")) def txcd_text(self, ar): if self.txcd_issuer == 'BBA': # until we get a list of German translations, users in # Eupen prefer FR over EN with translation.override('fr'): return force_text(code2desc(self.txcd[:4])) return "{0}:{1}".format(self.txcd_issuer, self.txcd)
class Enrolment(dd.Model): # invoiceable_date_field = 'request_date' # workflow_state_field = 'state' allow_cascaded_copy = 'order' allow_cascaded_delete = 'order' class Meta: app_label = 'orders' abstract = dd.is_abstract_model(__name__, 'Enrolment') verbose_name = _("Enrolment") verbose_name_plural = _('Enrolments') unique_together = ('order', 'worker') # order_layout = OrderLayouts.field(blank=True, editable=False) quick_search_fields = worker_name_fields #~ teacher = dd.ForeignKey(Teacher) order = dd.ForeignKey('orders.Order', related_name="enrolments_by_order") worker = dd.ForeignKey(worker_model, related_name="enrolments_by_worker") guest_role = dd.ForeignKey("cal.GuestRole", blank=True, null=True, verbose_name=_("Role in calendar entries")) remark = models.CharField(_("Remark"), max_length=200, blank=True) @dd.chooser() def worker_choices(cls, order): worker = dd.resolve_model(worker_model) return worker.objects.all() # def create_worker_choice(self, text): # """ # Called when an unknown worker name was given. # Try to auto-create it. # """ # worker = dd.resolve_model(worker_model) # kw = parse_name(text) # if len(kw) != 2: # raise ValidationError( # "Cannot find first and last names in %r to \ # auto-create worker", text) # p = worker(**kw) # p.full_clean() # p.save() # return p def get_overview_elems(self, ar): if self.order_id: return [self.order.obj2href(ar)] return [self.obj2href(ar)] def get_guest_role(self): return self.guest_role def make_guest_for(self, event): gr = self.get_guest_role() if gr is not None: return rt.models.cal.Guest(event=event, partner=self.worker, role=gr) def __str__(self): return "%s / %s" % (self.order, self.worker) def get_print_language(self): return self.worker.language def get_body_template(self): """Overrides :meth:`lino.core.model.Model.get_body_template`.""" return self.order.body_template def get_excerpt_title(self): return self.order.get_excerpt_title()
class CountryCity(dd.Model): """Model mixin that adds two fields `country` and `city` and defines a context-sensitive chooser for `city`, a `create_city_choice` method, ... .. attribute:: country .. attribute:: zip_code .. attribute:: city A pointer to :class:`Place`. """ class Meta: abstract = True country = dd.ForeignKey("countries.Country", blank=True, null=True) city = dd.ForeignKey('countries.Place', verbose_name=_('City'), blank=True, null=True) zip_code = models.CharField(_("Zip code"), max_length=10, blank=True) active_fields = 'city zip_code' # active fields cannot be used in insert_layout @dd.chooser() def city_choices(cls, country): return rt.modules.countries.Place.get_cities(country) @dd.chooser() def country_choices(cls): return rt.modules.countries.Country.get_actual_countries() def create_city_choice(self, text): """ Called when an unknown city name was given. Try to auto-create it. """ if self.country is not None: return rt.modules.countries.Place.lookup_or_create( 'name', text, country=self.country) raise ValidationError("Cannot auto-create city %r if country is empty", text) def country_changed(self, ar): """ If user changes the `country`, then the `city` gets lost. """ if self.city is not None and self.country != self.city.country: self.city = None def zip_code_changed(self, ar): if self.country and self.zip_code: qs = rt.modules.countries.Place.objects.filter( country=self.country, zip_code=self.zip_code) if qs.count() > 0: self.city = qs[0] def full_clean(self, *args, **kw): """Fills my :attr:`zip_code` from my :attr:`city` if my `zip_code` is not empty and differs from that of the city. """ city = self.city if city is None: self.zip_code_changed(None) else: if city.country is not None and self.country != city.country: self.country = city.country if city.zip_code: self.zip_code = city.zip_code super(CountryCity, self).full_clean(*args, **kw)
class Address(AddressLocation): class Meta: app_label = 'addresses' verbose_name = _("Address") verbose_name_plural = _("Addresses") quick_search_fields = "partner__name city__name street" data_source = DataSources.field(editable=False, default='manually') # address_type = AddressTypes.field(blank=True, null=True) address_type = AddressTypes.field(default='official') partner = dd.ForeignKey( dd.plugins.addresses.partner_model, related_name='addresses_by_partner') remark = dd.CharField(_("Remark"), max_length=50, blank=True) primary = models.BooleanField(_("Primary"), default=False) allow_cascaded_delete = ['partner'] def __str__(self): return self.address_location(', ') def after_ui_save(self, ar, cw): super(Address, self).after_ui_save(ar, cw) mi = self.partner if mi is None: return if self.primary: for o in mi.addresses_by_partner.exclude(id=self.id): if o.primary: o.primary = False o.save() ar.set_response(refresh_all=True) mi.sync_primary_address(ar.request) def living_at_text(self): lines = list(self.address_location_lines()) return self.address_type.living_text + ' ' + ', '.join(lines) @classmethod def setup_parameters(cls, fields): fields.update( place=dd.ForeignKey('countries.Place', blank=True, null=True)) super(Address, cls).setup_parameters(fields) @classmethod def get_request_queryset(cls, ar, **filter): qs = super(Address, cls).get_request_queryset(ar, **filter) pv = ar.param_values if pv.place: qs = qs.filter(city=pv.place) return qs @classmethod def get_title_tags(self, ar): for t in super(Address, self).get_title_tags(ar): yield t pv = ar.param_values if pv.place: yield str(pv.place) @classmethod def get_simple_parameters(cls): for p in super(Address, cls).get_simple_parameters(): yield p yield 'partner' yield 'address_type'
class Excerpt(mixins.TypedPrintable, UserAuthored, Controllable, mixins.ProjectRelated, ContactRelated, Mailable, Postable): """A printable document that describes some aspect of the current situation. .. attribute:: excerpt_type The type of this excerpt (ForeignKey to :class:`ExcerptType`). .. attribute:: owner The object being printed by this excerpt. See :attr:`Controllable.owner <lino.modlib.gfks.mixins.Controllable.owner>`. .. attribute:: company The optional company of the :attr:`recipient` of this excerpt. See :attr:`ContactRelated.company <lino.modlib.contacts.mixins.ContactRelated.company>`. .. attribute:: contact_person The optional contact person of the :attr:`recipient` of this excerpt. See :attr:`ContactRelated.contact_person <lino.modlib.contacts.mixins.ContactRelated.contact_person>`. .. attribute:: recipient The recipient of this excerpt. See :attr:`ContactRelated.recipient <lino.modlib.contacts.mixins.ContactRelated.recipient>` .. attribute:: language The language used for printing this excerpt. .. attribute:: date .. attribute:: time .. method:: get_address_html See :meth:`lino.modlib.contacts.mixins.ContactRelated.get_address_html`. Return the address of the :attr:`recipient` of this excerpt. """ manager_roles_required = dd.login_required(OfficeStaff) # manager_level_field = 'office_level' allow_cascaded_delete = "owner" class Meta: abstract = dd.is_abstract_model(__name__, 'Excerpt') verbose_name = _("Excerpt") verbose_name_plural = _("Excerpts") excerpt_type = dd.ForeignKey('excerpts.ExcerptType') body_template_content = BodyTemplateContentField(_("Body template")) language = dd.LanguageField() # if dd.is_installed('outbox'): # mails_by_owner = dd.ShowSlaveTable('outbox.MailsByController') def get_body_template(self): """Return the body template to use for this excerpt.""" owner = self.owner # owner is None e.g. if is a broken GFK if owner is not None: assert self.__class__ is not owner.__class__ tplname = owner.get_body_template() if tplname: return tplname return self.excerpt_type.body_template def get_body_template_filename(self): tplname = self.get_body_template() if not tplname: return None mc = self.excerpt_type.content_type.model_class() tplgroup = mc.get_template_group() return rt.find_config_file(tplname, tplgroup) def get_body_template_name(self): tplname = self.get_body_template() if not tplname: return None mc = self.excerpt_type.content_type.model_class() tplgroup = mc.get_template_group() return tplgroup + '/' + tplname def disabled_fields(self, ar): rv = super(Excerpt, self).disabled_fields(ar) rv = rv | set(['excerpt_type', 'project']) if self.build_time: rv |= self.PRINTABLE_FIELDS return rv def __unicode__(self): if self.build_time: return naturaltime(self.build_time) # return _("%(owner)s (printed %(time)s)") % dict( # owner=self.owner, time=naturaltime(self.build_time)) return _("Unprinted %s #%s") % (self._meta.verbose_name, self.pk) def get_mailable_type(self): return self.excerpt_type def get_mailable_subject(self): return unicode(self.owner) # .get_mailable_subject() def get_template_groups(self): ptype = self.get_printable_type() if ptype is None: raise Exception("20140520 Must have excerpt_type.") grp = ptype.content_type.model_class().get_template_group() return [grp, 'excerpts'] def filename_root(self): # mainly because otherwise we would need to move files around on # existing sites et = self.excerpt_type if et is None or not et.certifying: return super(Excerpt, self).filename_root() o = self.owner return o._meta.app_label + '.' + o.__class__.__name__ + '-' + str(o.pk) def get_print_templates(self, bm, action): et = self.excerpt_type if et is not None and et.certifying: tpls = self.owner.get_excerpt_templates(bm) if tpls is not None: return tpls return super(Excerpt, self).get_print_templates(bm, action) # ptype = self.get_printable_type() # # raise Exception("20150710 %s" % self.owner) # if ptype is not None and ptype.template: # return [ptype.template] # # return [bm.get_default_template(self)] # return [dd.plugins.excerpts.get_default_template(bm, self.owner)] # def get_recipient(self): # rec = super(Excerpt, self).get_recipient() # if rec is None and hasattr(self.owner, 'recipient'): # return self.owner.recipient # return rec # recipient = property(get_recipient) def get_printable_type(self): return self.excerpt_type def get_print_language(self): return self.language def unused_on_create(self, ar): # replaced by signal below super(Excerpt, self).on_create(ar) if not self.owner_id: if self.project: self.owner = self.project self.language = self.owner.get_print_language() @dd.chooser() def excerpt_type_choices(cls, owner): # logger.info("20150702 %s", owner) qs = rt.modules.excerpts.ExcerptType.objects.order_by('name') if owner is None: # e.g. when choosing on the *parameter* field # return qs.filter(content_type__isnull=True) return qs.filter() ct = ContentType.objects.get_for_model(owner.__class__) return qs.filter(content_type=ct) @property def date(self): "Used in templates" if self.build_time: return self.build_time.date() return dd.today() @property def time(self): "Used in templates" if self.build_time: return self.build_time.time() return datetime.datetime.now() @dd.virtualfield(dd.HtmlBox(_("Preview"))) def preview(self, ar): with translation.override(self.get_print_language()): ctx = self.get_printable_context(ar) return '<div class="htmlText">%s</div>' % ctx['body'] def get_printable_context(self, ar=None, **kw): """Adds a series of names to the context used when rendering printable documents. See :doc:`/user/templates_api`. """ if self.owner is not None: kw = self.owner.get_printable_context(ar, **kw) kw = super(Excerpt, self).get_printable_context(**kw) kw.update(obj=self.owner) body = '' if self.excerpt_type_id is not None: etype = self.excerpt_type if etype.backward_compat: kw.update(this=self.owner) tplname = self.get_body_template_name() if tplname and ar is not None: # sar = copy(ar) # sar.renderer = settings.SITE.kernel.html_renderer env = settings.SITE.plugins.jinja.renderer.jinja_env template = env.get_template(tplname) # logger.info("body template %s (%s)", tplname, template) body = ar.render_jinja(template, **kw) # logger.info("20150811 body template %s (%s) -> %s", # tplname, template, body) kw.update(body=body) return kw @classmethod def on_analyze(cls, site): cls.PRINTABLE_FIELDS = dd.fields_list( cls, "project excerpt_type " "body_template_content " "company contact_person language " "user build_method") super(Excerpt, cls).on_analyze(site)
app_label = 'coachings' verbose_name = _("Client Contact") verbose_name_plural = _("Client Contacts") #~ type = ClientContactTypes.field(blank=True) client = dd.ForeignKey(client_model) remark = models.TextField(_("Remarks"), blank=True) # ,null=True) def full_clean(self, *args, **kw): if not self.remark and not self.type \ and not self.company and not self.contact_person: raise ValidationError(_("Must fill at least one field.")) super(ClientContact, self).full_clean(*args, **kw) dd.update_field(ClientContact, 'contact_person', verbose_name=_("Contact person")) contacts = dd.resolve_app('contacts') dd.inject_field( 'contacts.Partner', 'client_contact_type', dd.ForeignKey('coachings.ClientContactType', blank=True, null=True)) class PartnersByClientContactType(contacts.Partners): master_key = 'client_contact_type' column_names = "name address_column phone gsm email *" auto_fit_column_widths = True
class ExcerptType(mixins.BabelNamed, mixins.PrintableType, MailableType): """The type of an excerpt. Every excerpt has a mandatory field :attr:`Excerpt.excerpt_type` which points to an :class:`ExcerptType` instance. .. attribute:: name The designation of this excerpt type. One field for every :attr:`language <lino.core.site.Site.language>`. .. attribute:: content_type The database model for which this excerpt type is to be used. .. attribute:: build_method See :attr:`lino.mixinsPrintableType.build_method`. .. attribute:: template The main template to be used when printing an excerpt of this type. .. attribute:: body_template The body template to use when printing an excerpt of this type. .. attribute:: email_template The template to use when sending this an excerpt of this type by email. .. attribute:: shortcut Optional pointer to a shortcut field. If this is not empty, then the given shortcut field will manage excerpts of this type. See also :class:`Shortcuts`. See also :class:`lino.modlib.excerpts.choicelists.Shortcuts`. """ # templates_group = 'excerpts/Excerpt' class Meta: abstract = dd.is_abstract_model(__name__, 'ExcerptType') verbose_name = _("Excerpt Type") verbose_name_plural = _("Excerpt Types") certifying = models.BooleanField( verbose_name=_("Certifying"), default=False, help_text=_("Whether an excerpt of this type is a unique printout.")) remark = models.TextField(verbose_name=_("Remark"), blank=True) body_template = models.CharField( max_length=200, verbose_name=_("Body template"), blank=True, help_text="The body template to be used when \ rendering a printable of this type. This is a list of files \ with extension `.body.html`.") content_type = dd.ForeignKey( 'contenttypes.ContentType', verbose_name=_("Model"), related_name='excerpt_types', # null=True, blank=True, help_text=_("The model that can issue printouts of this type.")) """The model on which excerpts of this type are going to work.""" primary = models.BooleanField( _("Primary"), default=False, help_text=_("""There's at most one primary type per model. \ Enabling this field will automatically make the other \ types non-primary.""")) backward_compat = models.BooleanField( _("Backward compatible"), default=False, help_text=_("Check this to have `this` in template context " "point to owner instead of excerpt.")) print_recipient = models.BooleanField( _("Print recipient"), default=True, help_text=_("Whether to print a recipient field in document.")) print_directly = models.BooleanField(_("Print directly"), default=True) shortcut = Shortcuts.field(blank=True) def full_clean(self, *args, **kwargs): if self.certifying: if not self.primary: raise ValidationError( _("Cannot set %(c)s without %(p)s") % dict(c=_("Certifying"), p=_("Primary"))) mc = self.content_type.model_class() if not issubclass(mc, Certifiable): raise ValidationError( _("Cannot set %(c)s for non.certifiable " "model %(m)s") % dict(c=_("Certifying"), m=mc._meta.verbose_name)) super(ExcerptType, self).full_clean(*args, **kwargs) def update_siblings(self): updated = 0 if self.primary: for o in self.content_type.excerpt_types.exclude(id=self.id): if o.primary: o.primary = False o.save() updated += 1 return updated def save(self, *args, **kwargs): # It is important to ensure that there is really only one # primary ExcerptType per model because # :func:`set_excerpts_actions` will install these as action on # the model. super(ExcerptType, self).save(*args, **kwargs) self.update_siblings() def after_ui_save(self, ar, cw): super(ExcerptType, self).after_ui_save(ar, cw) if self.primary: ar.set_response(refresh_all=True) @classmethod def get_template_groups(cls): raise Exception("""Not used by ExcerptType. \ We override everything in Excerpt to not call the class method.""") @dd.chooser(simple_values=True) def template_choices(cls, build_method, content_type): tplgroups = [ content_type.model_class().get_template_group(), 'excerpts' ] return cls.get_template_choices(build_method, tplgroups) @dd.chooser(simple_values=True) def body_template_choices(cls, content_type): # 20140617 don't remember why the "excerpts" group was useful here # tplgroups = [model_group(content_type.model_class()), 'excerpts'] # return dd.plugins.jinja.list_templates('.body.html', *tplgroups) tplgroup = content_type.model_class().get_template_group() return dd.plugins.jinja.list_templates('.body.html', tplgroup, 'excerpts') @dd.chooser(simple_values=True) def email_template_choices(cls, content_type): tplgroup = content_type.model_class().get_template_group() return dd.plugins.jinja.list_templates('.eml.html', tplgroup, 'excerpts') @classmethod def get_for_model(cls, model): "Return the primary ExcerptType for the given model." ct = ContentType.objects.get_for_model(dd.resolve_model(model)) return cls.objects.get(primary=True, content_type=ct) @classmethod def update_for_model(cls, model, **kw): obj = cls.get_for_model(model) for k, v in kw.items(): setattr(obj, k, v) # obj.full_clean() # obj.save() return obj def get_or_create_excerpt(self, ar): obj = ar.selected_rows[0] model = self.content_type.model_class() if not isinstance(obj, model): raise Exception("%s is not an instance of %s" % (obj, model)) Excerpt = rt.modules.excerpts.Excerpt ex = None if self.certifying: qs = Excerpt.objects.filter( excerpt_type=self, owner_id=obj.pk, owner_type=ContentType.objects.get_for_model(obj.__class__)) qs = qs.order_by('id') if qs.count() > 0: ex = qs[0] if ex is None: akw = dict(user=ar.get_user(), owner=obj, excerpt_type=self) akw = obj.get_excerpt_options(ar, **akw) ex = Excerpt(**akw) ex.on_create(ar) ex.full_clean() ex.save() if self.certifying: obj.printed_by = ex obj.full_clean() obj.save() return ex def get_action_name(self): if self.primary: return 'do_print' else: return 'create_excerpt' + str(self.pk) @dd.displayfield(_("Model")) def content_type_display(self, ar): if ar is None: return '' model = self.content_type.model_class() label = "{0} ({1})".format(dd.full_model_name(model), model._meta.verbose_name) return ar.obj2html(self.content_type, label)
class Note(dd.Model): owner_type = dd.ForeignKey(ContentType) owner_id = models.PositiveIntegerField() owner = dd.GenericForeignKey('owner_type', 'owner_id') text = models.CharField(max_length=200)
class Uploads(Uploads): column_names = 'user project type file start_date end_date ' \ 'description_link *' detail_layout = UploadDetail() insert_layout = """ type file start_date end_date description """ parameters = mixins.ObservedDateRange( # puser=dd.ForeignKey( # 'users.User', blank=True, null=True, # verbose_name=_("Uploaded by")), upload_type=dd.ForeignKey('uploads.UploadType', blank=True, null=True), coached_by=dd.ForeignKey( 'users.User', blank=True, null=True, verbose_name=_("Coached by"), help_text=_( "Show only uploads for clients coached by this user.")), observed_event=dd.PeriodEvents.field( _("Validity"), blank=True, default=dd.PeriodEvents.as_callable('active'))) params_layout = "observed_event:20 start_date end_date \ coached_by user upload_type" auto_fit_column_widths = True @classmethod def get_request_queryset(cls, ar, **kwargs): # (why was this?) use inherited method from grandparent (not # direct parent) # qs = super(LibraryUploads, cls).get_request_queryset(ar) qs = super(Uploads, cls).get_request_queryset(ar, **kwargs) pv = ar.param_values ce = pv.observed_event if ce is not None: qs = ce.add_filter(qs, pv) # if pv.puser: # qs = qs.filter(user=pv.puser) if pv.coached_by: qs = qs.filter(project__coachings_by_client__user=pv.coached_by) # MyExpiringUploads wants only needed uploads qs = qs.filter(needed=True) # if pv.pupload_type: # qs = qs.filter(type=pv.pupload_type) return qs @classmethod def get_title_tags(self, ar): for t in super(Uploads, self).get_title_tags(ar): yield t pv = ar.param_values if pv.observed_event: yield str(pv.observed_event) if pv.coached_by: yield str(self.parameters['coached_by'].verbose_name) + \ ' ' + str(pv.coached_by) if pv.user: yield str(self.parameters['user'].verbose_name) + \ ' ' + str(pv.user)
from lino_cosi.lib.contacts.models import Partners class PartnersByInvoiceRecipient(Partners): label = _("Invoice senders") master_key = 'invoice_recipient' column_names = 'name id address_column *' window_size = (50, 15) params_panel_hidden = True dd.inject_field( 'contacts.Partner', 'invoice_recipient', dd.ForeignKey('contacts.Partner', verbose_name=_("Invoicing address"), blank=True, null=True, help_text=_("Redirect to another partner all invoices which " "should go to this partner."))) dd.inject_action( 'contacts.Partner', show_invoice_partners=dd.ShowSlaveTable(PartnersByInvoiceRecipient)) dd.inject_field('contacts.Partner', 'paper_type', dd.ForeignKey('sales.PaperType', null=True, blank=True)) # class Channels(dd.ChoiceList): # label = _("Channel") # add = Channels.add_item # add('P', _("Paper"), 'paper') # add('E', _("E-mail"), 'email')
class Link(dd.Model): """A link between two persons. .. attribute:: parent Pointer to the person who is "parent". .. attribute:: child Pointer to the person who is "child". .. attribute:: type The type of link. Pointer to :class:`LinkTypes <lino_xl.lib.humanlinks.choicelists.LinkTypes>`. """ class Meta: verbose_name = _("Personal Link") verbose_name_plural = _("Personal Links") type = LinkTypes.field(default=LinkTypes.as_callable('parent')) parent = dd.ForeignKey(config.person_model, verbose_name=_("Who is..."), related_name='humanlinks_children') child = dd.ForeignKey(config.person_model, blank=True, null=True, verbose_name=_("To whom..."), related_name='humanlinks_parents') @dd.displayfield(_("Type")) def type_as_parent(self, ar): # print('20140204 type_as_parent', self.type) return self.type.as_parent(self.parent) @dd.displayfield(_("Type")) def type_as_child(self, ar): # print('20140204 type_as_child', self.type) return self.type.as_child(self.child) def __str__(self): if self.type is None: return super(Link, self).__str__() return _("%(child)s is %(what)s") % dict( child=str(self.child), what=self.type_of_parent_text()) def type_of_parent_text(self): return _("%(type)s of %(parent)s") % dict( parent=self.parent, type=self.type.as_child(self.child)) parent_link_types = (LinkTypes.parent, LinkTypes.adoptive_parent, LinkTypes.foster_parent) @classmethod def check_autocreate(cls, parent, child): """Check whether there is a human link of type "parent" between the given persons. Create one if not. If the child has already another parent of same sex, then it becomes a foster child, otherwise a natural child. Note that the ages are ignored here, Lino will shamelessly create a link even when the child is older than the parent. This is called from :meth:`full_clean` of :class:`lino_xl.lib.households.Member` to automatically create human links between two household members. """ if parent is None or child is None: return False if parent == child: return False # raise ValidationError("Parent and Child must differ") qs = cls.objects.filter(parent=parent, child=child, type__in=cls.parent_link_types) if qs.count() == 0: qs = cls.objects.filter(child=child, type__in=cls.parent_link_types, parent__gender=parent.gender) if qs.count() == 0: auto_type = LinkTypes.parent else: auto_type = LinkTypes.foster_parent obj = cls(parent=parent, child=child, type=auto_type) obj.full_clean() obj.save() # dd.logger.info("20141018 autocreated %s", obj) return True return False
class Certifiable(dd.Model): """Any model which inherits from this mixin becomes "certifiable". That is: - it has a `printed_by` field and a corresponding virtual field `printed` which point to the excerpt that is the "definitive" ("Certifying") printout of this object. - It may define a list of "certifiable" fields. See :meth:`get_certifiable_fields`. Usage example:: from lino_xl.lib.excerpts.mixins import Certifiable class MyModel(dd.UserAuthored, Certifiable, dd.Duplicable): ... The :mod:`lino_xl.lib.excerpts.fixtures.std` fixture automatically creates a certifying :class:`ExcerptType` instance for every model which inherits from :class:`Certifiable`. .. attribute:: printed Displays information about when this certifiable has been printed. Clicking on it will display the excerpt pointed to by :attr:`printed_by`. .. attribute:: printed_by ForeignKey to the :class:`Excerpt` which certifies this instance. A :class:`Certifiable` is considered "certified" when this this is not `None`. Note that this field is a nullable ForeignKey with `on_delete <https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.ForeignKey.on_delete>`__ set to ``SET_NULL``. """ class Meta: abstract = True printed_by = dd.ForeignKey( 'excerpts.Excerpt', verbose_name=_("Printed"), editable=False, related_name="%(app_label)s_%(class)s_set_as_printed", blank=True, null=True, on_delete=models.SET_NULL) clear_printed = ClearPrinted() def disabled_fields(self, ar): if self.printed_by_id is None: return set() return self.CERTIFIED_FIELDS def on_duplicate(self, ar, master): """After duplicating e.g. a budget which had been printed, we don't want the duplicate point to the same excerpt. :meth:`lino.mixins.duplicable.Duplicable.on_duplicate`. """ super(Certifiable, self).on_duplicate(ar, master) self.printed_by = None @classmethod def on_analyze(cls, site): # Contract.user.verbose_name = _("responsible (DSBE)") cls.CERTIFIED_FIELDS = dd.fields_list(cls, cls.get_certifiable_fields()) super(Certifiable, cls).on_analyze(site) @classmethod def get_printable_demo_objects(cls, excerpt_type): """Return an iterable of database objects for which Lino should generate a printable excerpt. This is being called by :mod:`lino_xl.lib.excerpts.fixtures.demo2`. """ qs = cls.objects.all() if qs.count() > 0: yield qs[0] @classmethod def get_certifiable_fields(cls): """ Expected to return a string with a space-separated list of field names. These files will automaticaly become disabled (readonly) when the document is "certified". The default implementation returns an empty string, which means that no field will become disabled when the row is "certified". For example:: @classmethod def get_certifiable_fields(cls): return 'date user title' """ return '' @dd.displayfield(_("Printed")) def printed(self, ar): if ar is None: return '' ex = self.printed_by if ex is None: return '' return ar.obj2html(ex, naturaltime(ex.build_time)) def clear_cache(self): obj = self.printed_by if obj is not None: self.printed_by = None self.full_clean() self.save() obj.delete() def get_excerpt_title(self): """A string to be used in templates as the title of the certifying document. """ return unicode(self) def get_excerpt_templates(self, bm): """Return either `None` or a list of template names to be used when printing an excerpt controlled by this object. """ return None
class Transaction(dd.Model): class Meta: app_label = 'b2c' abstract = dd.is_abstract_model(__name__, 'Transaction') verbose_name = _("Transaction") verbose_name_plural = _("Transactions") statement = dd.ForeignKey('b2c.Statement') seqno = models.IntegerField( _('No.'), help_text=_( "The sequence number of this transaction in the statement.")) # unique_import_id = models.CharField(_('Unique import ID'), max_length=128) # movement_number = models.CharField(_("Ref of Mov"), null=False, max_length=32) # movement_date = models.DateField(_('Movement date'), null=True, blank=True) amount = dd.PriceField(_('Amount'), null=True, blank=True) # partner = dd.ForeignKey('contacts.Partner', related_name='b2c_movement', null=True) # partner_name = models.CharField(_('Partner name'), max_length=35, blank=True) remote_account = models.CharField(_("IBAN"), blank=True, max_length=64) remote_bic = BICField(verbose_name=_("BIC"), blank=True) # ref = models.CharField(_('Ref'), max_length=35, blank=True) message = models.TextField(_('Message'), blank=True) eref = models.CharField(_('End to end reference'), max_length=128, blank=True) remote_owner = models.CharField(_('Remote owner'), max_length=128, blank=True) remote_owner_address = models.TextField(_('Remote owner adress'), blank=True) remote_owner_city = models.CharField(_('Remote owner city'), max_length=32, blank=True) remote_owner_postalcode = models.CharField(_('Remote owner postal code'), max_length=10, blank=True) remote_owner_country_code = models.CharField( _('Remote owner country code'), max_length=4, blank=True) txcd = models.CharField(_('Transfer type'), max_length=32, blank=True) txcd_issuer = models.CharField(_('TxCd issuer'), max_length=35, blank=True) booking_date = models.DateField(_('Execution date'), null=True, blank=True) value_date = models.DateField(_('Value date'), null=True, blank=True) @dd.displayfield(_("Remote account")) def remote_html(self, ar): elems = [] elems += [self.remote_account, " "] elems += ["(BIC:", self.remote_bic, ")"] elems.append(E.br()) elems += [E.b(self.remote_owner), ", "] elems.append(E.br()) elems += [" / ".join(self.remote_owner_address.splitlines()), ", "] elems += [self.remote_owner_postalcode, " "] elems += [self.remote_owner_city, " "] elems += [self.remote_owner_country_code] return E.div(*elems) @dd.displayfield(_("Message")) def message_html(self, ar): from django.utils.translation import ugettext as _ elems = [] # elems += [_("Date"), dd.fds(self.transaction_date), " "] # elems += [_("Amount"), ' ', E.b(unicode(self.amount)), " "] # self.booking_date elems += self.message # .splitlines() elems.append(E.br()) # elems += [_("ref:"), ': ', self.ref, ' '] elems += [_("eref:"), ': ', self.eref] elems.append(E.br()) elems += [E.b(self.txcd_text), ' '] elems += [_("Value date"), ': ', E.b(dd.fds(self.value_date)), " "] elems += [_("Booking date"), ': ', E.b(dd.fds(self.booking_date)), " "] return E.div(*elems) @dd.displayfield(_("BkTxCd")) def txcd_text(self, ar): if self.txcd_issuer == 'BBA': # until we get a list of German translations, users in # Eupen prefer FR over EN with translation.override('fr'): return force_text(code2desc(self.txcd[:4])) return "{0}:{1}".format(self.txcd_issuer, self.txcd)