class Volume(Referrable): class Meta: app_label = 'uploads' verbose_name = _("Library volume") verbose_name_plural = _("Library volumes") preferred_foreignkey_width = 5 root_dir = dd.CharField(_("Root directory"), max_length=255) base_url = dd.CharField(_("Base URL"), max_length=255, blank=True) description = dd.CharField(_("Description"), max_length=255, blank=True) def __str__(self): return self.ref or self.root_dir def get_filenames(self): root_len = len(self.root_dir) + 1 for (root, dirs, files) in os.walk(self.root_dir): relroot = root[root_len:] if relroot: relroot += "/" for fn in files: # print(relroot + "/" + fn) yield relroot + fn
class Matching(dd.Model): class Meta: abstract = True match = dd.CharField( _("Match"), max_length=20, blank=True, help_text=_("The movement to be matched.")) @classmethod def get_match_choices(cls, journal, partner): """This is the general algorithm. """ matchable_accounts = rt.models.ledger.Account.objects.filter( matchrule__journal=journal) fkw = dict(account__in=matchable_accounts) fkw.update(cleared=False) if partner: fkw.update(partner=partner) qs = rt.models.ledger.Movement.objects.filter(**fkw) qs = qs.order_by('value_date') # qs = qs.distinct('match') return qs.values_list('match', flat=True) @dd.chooser(simple_values=True) def match_choices(cls, journal, partner): # todo: move this to implementing classes? return cls.get_match_choices(journal, partner) def get_match(self): # return self.match or self.get_default_match() return self.match or self # 20191226
class SignIn(dd.Action): label = _("Sign in") select_rows = False parameters = dict(username=dd.CharField(_("Username")), password=dd.PasswordField(_("Password"), blank=True)) # params_layout = dd.ActionParamsLayout(""" params_layout = dd.Panel(""" username password """, label_align=layouts.LABEL_ALIGN_LEFT) http_method = "POST" def run_from_ui(self, ar, **kw): pv = ar.action_param_values user = auth.authenticate(ar.request, username=pv.username, password=pv.password) if user is None: ar.error(_("Failed to log in as {}.".format(pv.username))) else: # user.is_authenticated: auth.login(ar.request, user) ar.success(_("Now logged in as {}").format(user), close_window=True, goto_url=ar.renderer.front_end.build_plain_url())
class ContactDetail(dd.Model): class Meta: app_label = 'phones' verbose_name = _("Contact detail") verbose_name_plural = _("Contact details") detail_type = ContactDetailTypes.field(default='email') partner = dd.ForeignKey( dd.plugins.phones.partner_model, related_name='phones_by_partner') value = dd.CharField(_("Value"), max_length=200, blank=True) remark = dd.CharField(_("Remark"), max_length=200, blank=True) primary = models.BooleanField(_("Primary"), default=False) end_date = models.DateField(_("Until"), blank=True, null=True) allow_cascaded_delete = ['partner'] def __str__(self): return self.detail_type.format(self.value) def full_clean(self): super(ContactDetail, self).full_clean() self.detail_type.validate(self.value) def after_ui_save(self, ar, cw): super(ContactDetail, self).after_ui_save(ar, cw) mi = self.partner if mi is None: return if self.primary and self.detail_type: for o in mi.phones_by_partner.exclude(id=self.id).filter( detail_type=self.detail_type): if o.primary: o.primary = False o.save() ar.set_response(refresh_all=True) k = self.detail_type.field_name if k: watcher = ChangeWatcher(mi) setattr(mi, k, self.value) watcher.send_update(ar) mi.save() @classmethod def get_simple_parameters(cls): return ['partner', 'detail_type']
class Change(dd.Model): """A registered change in the database. Each database change of a watched object will generate one Change record. .. attribute:: master The database object which acts as "master". .. attribute:: object The database object which has been modified. """ class Meta(object): verbose_name = _("Change") verbose_name_plural = _("Changes") # allow_cascaded_delete = 'master' quick_search_fields = 'changed_fields diff' show_in_site_search = False time = models.DateTimeField() type = ChangeTypes.field() if settings.SITE.user_model: user = dd.ForeignKey(settings.SITE.user_model) else: user = dd.DummyField() object_type = dd.ForeignKey('contenttypes.ContentType', blank=True, null=True, verbose_name=_("Object type"), related_name='changes_by_object') object_id = GenericForeignKeyIdField(object_type, blank=True, null=True) object = GenericForeignKey('object_type', 'object_id', _("Object")) master_type = dd.ForeignKey('contenttypes.ContentType', blank=True, null=True, verbose_name=_("Master type"), related_name='changes_by_master') master_id = GenericForeignKeyIdField(master_type, blank=True, null=True) master = GenericForeignKey('master_type', 'master_id', _("Master")) diff = dd.RichTextField(_("Changes"), format='plain', blank=True, editable=False) changed_fields = dd.CharField(_("Fields"), max_length=250, blank=True) def __str__(self): # ~ return "#%s - %s" % (self.id,self.time) return "#%s" % self.id
class ChatGroup(UserAuthored, Created, Referrable): class Meta(object): app_label = 'chat' verbose_name = _("Chat group") verbose_name_plural = _("Chat groups") ordering = ['created', 'id'] title = dd.CharField(max_length=20) description = dd.RichTextField(max_length=200, blank=True, null=True) ticket = dd.ForeignKey("tickets.Ticket", blank=True, null=True) @dd.action(_("getChatGroups")) def getChatGroups(self, ar): """ Returns info on all GroupChats for this user. """ qs = ChatGroupMember.objects.filter( user=ar.get_user()).select_related("group") rows = [{ "id": cp.group.pk, "title": cp.group.title, "unseen": cp.group.get_unseen_count(ar) } for cp in qs] return ar.success(rows=rows) def get_unseen_count(self, ar): """ Returns count of messages that haven't been seen yet.""" return ChatProps.objects.filter(chat__group=self, user=ar.get_user(), seen__isnull=True).count() @dd.action(_("Load GroupChat")) def loadGroupChat(self, ar): """Returns chat messages for a given chat""" rows = [] if 'mk' in ar.rqdata: # master = rt.models.resolve("contenttypes.ContentType").get(pk=ar.rqdata['mt']).get(pk=ar.rqdata["mk"]) ar.selected_rows = [ ChatGroup.objects.get(ticket__pk=ar.rqdata['mk']) ] for group in ar.selected_rows: last_ten = ChatProps.objects.filter( user=ar.get_user(), chat__group=group).order_by('-created').select_related("chat") rows.append({ 'title': group.title, 'id': group.id, 'messages': [cp.serialize(ar) for cp in last_ten] }) return ar.success(rows=rows)
class Foo(dd.Model): name = models.CharField(max_length=100, blank=True) remarks = models.TextField("Remarks", blank=True) input_mask_test = dd.CharField( "Question text", blank=True, max_length=200, help_text="""This field is here to play with the CharField parameters regex, mask_re and strip_chars_re. By default it accepts all letters except Z. """, #~ regex='/^[a-yA-Y]*$/') mask_re='/^[a-yA-Y]*$/')
class Matching(dd.Model): """Model mixin for database objects that are considered *matching transactions*. A **matching transaction** is a transaction that points to some other movement which it "clears" at least partially. A movement is cleared when its amount equals the sum of all matching movements. Adds a field :attr:`match` and a chooser for it. Requires a field `partner`. The default implementation of the chooser for :attr:`match` requires a `journal`. Base class for :class:`lino_cosi.lib.vat.AccountInvoice` (and e.g. `lino_cosi.lib.sales.Invoice`, `lino_cosi.lib.finan.DocItem`) .. attribute:: match Pointer to the :class:`movement <lino.modlib.ledger.models.Movement>` which is being cleared by this movement. """ class Meta: abstract = True match = dd.CharField(_("Match"), max_length=20, blank=True, help_text=_("The movement to be matched.")) @classmethod def get_match_choices(cls, journal, partner): """This is the general algorithm. """ matchable_accounts = rt.modules.accounts.Account.objects.filter( matchrule__journal=journal) fkw = dict(account__in=matchable_accounts) fkw.update(cleared=False) if partner: fkw.update(partner=partner) qs = rt.modules.ledger.Movement.objects.filter(**fkw) qs = qs.order_by('value_date') # qs = qs.distinct('match') return qs.values_list('match', flat=True) @dd.chooser(simple_values=True) def match_choices(cls, journal, partner): # todo: move this to implementing classes? return cls.get_match_choices(journal, partner)
class GooglePeople(dd.Model): class Meta: abstract = True google_resourceName = dd.CharField(max_length=200, verbose_name=_('Google ResourceName'), blank=True) google_contactID = dd.CharField(max_length=200, verbose_name=_('Google Contact ID'), blank=True) def save(self, *args, **kw): if not self.google_resourceName and self.name: body = {'names': [{'displayName': self.name, "givenName": self.last_name, "familyName": self.first_name}]} if self.email: body['emailAddresses'] = [{'value': self.email, 'type': 'work'}] if dd.is_installed('phones'): body.update( {'PhoneNumber': [{'value': self.phone, 'type': 'main'}, {'value': self.gsm, 'type': 'mobile'}]}) try: results = service.people().createContact(body=body).execute() if results and results.get('resourceName', False): self.google_resourceName = results.get('resourceName', False) self.google_contactID = results.get('resourceName', False).split('/')[1] except HttpError as e: print(e.content) elif self.google_resourceName: try: contactToUpdate = service.people().get(resourceName=self.google_resourceName, personFields='names,emailAddresses').execute() contactToUpdate['names'] = [ {'displayName': self.name, "givenName": self.last_name, "familyName": self.first_name}] service.people().updateContact(resourceName=self.google_resourceName, updatePersonFields='names,emailAddresses', body=contactToUpdate).execute() except HttpError as e: print(e.content) res = super(GooglePeople, self).save(*args, **kw) return res
class SignInWithSocialAuth(SignIn): # 20171207 nice as an example of a action dialog window with a # HtmlBox, but we don't currently use it. parameters = dict( social_auth_links=dd.HtmlBox( # _("Other authentications"), default=E.div(*settings.SITE.get_social_auth_links())), # social_auth_links=dd.Constant( # settings.SITE.get_social_auth_links), # social=social_auth_field(), username=dd.CharField(_("Username")), password=dd.PasswordField(_("Password"), blank=True) ) # params_layout = dd.ActionParamsLayout(""" params_layout = dd.Panel(""" username password social_auth_links """, label_align="left", window_size=(60,10))
class Plan(Referrable): ref_max_length = 30 class Meta: app_label = 'healthcare' abstract = dd.is_abstract_model(__name__, 'Plan') verbose_name = _("Healthcare plan") verbose_name_plural = _("Healthcare plans") provider = dd.ForeignKey('contacts.Company', related_name="healthcare_plans_by_provider", verbose_name=_("Provider"), blank=True, null=True) remark = dd.CharField(_("Remark"), max_length=200, blank=True) def __str__(self): if self.ref: return self.ref else: return str(self.provider)
class Tag(dd.Model): name = dd.CharField(max_length=100) def __str__(self): return self.name
class Meeting(Referrable, Milestone, Reservation, Duplicable, Starrable): """A Meetings is a list of tickets that are to be discussed with a group of people """ # .. attribute:: max_places # # Available places. The maximum number of participants to allow # in this course. # # """ workflow_state_field = 'state' class Meta: app_label = 'meetings' verbose_name = _("Meeting") verbose_name_plural = _('Meetings') # verbose_name = _("Event") # verbose_name_plural = _('Events') description = dd.RichTextField(_("Description"), blank=True) child_starrables = [('deploy.Deployment', 'milestone', 'ticket')] quick_search_fields = 'name description ref' site_field_name = 'site' site = dd.ForeignKey('tickets.Site', blank=True, null=True) state = MeetingStates.field(default=MeetingStates.as_callable('draft')) name = dd.CharField(_("Title"), max_length=100, blank=True) def on_duplicate(self, ar, master): # self.state = CourseStates.draft # def OK(ar): self.state = MeetingStates.draft # ar.confirm(OK,_("Remove inactive tickets on new meeting?")) old = ar.selected_rows[0] if self.ref: old.ref = datetime.now().strftime("%Y%m%d") + "@" + old.ref old.full_clean() old.save() super(Referrable, self).on_duplicate(ar, master) def after_duplicate(self, ar, master): rt.models.deploy.Deployment.objects.filter( Q(milestone=self), Q(new_ticket_state__in=TicketStates.filter(active=False)) | Q(ticket__state__in=TicketStates.filter(active=False))).delete() rt.models.deploy.Deployment.objects.filter(milestone=self).update( new_ticket_state=None, old_ticket_state=None, remark="", ) stars = rt.models.stars.Star.for_obj( master, ) #no master__isnull since we want to copy the site star for s in stars: s.owner = self s.id = None s.save() def __str__(self): if self.ref: return self.ref if self.name: return self.name if self.room is None: return "(%s)" % (dd.fds(self.start_date)) # Note that we cannot use super() with # python_2_unicode_compatible return "(%s@%s)" % (dd.fds(self.start_date), self.room) def update_cal_from(self, ar): """Note: if recurrency is weekly or per_weekday, actual start may be later than self.start_date """ return self.start_date def update_cal_event_type(self): return None def full_clean(self, *args, **kw): super(Meeting, self).full_clean(*args, **kw) def get_milestone_users(self): #todo if dd.is_installed("stars"): for s in rt.models.stars.Star.for_obj(self): # u = obj.partner.get_as_user() # if u is not None: yield s.user def site_changed(self, ar): """Leaves a sub-star of old site, but that's OK for now""" if self.site is not None: self.site.add_child_stars(self.site, self) # self.add_change_watcher(star.user) def after_ui_create(self, ar): self.site_changed(ar) super(Meeting, self).after_ui_create(ar) @classmethod def add_param_filter(cls, qs, lookup_prefix='', show_active=None, **kwargs): qs = super(Meeting, cls).add_param_filter(qs, **kwargs) active_states = MeetingStates.filter(active=True) fkw = dict() fkw[lookup_prefix + 'state__in'] = active_states if show_active == dd.YesNo.no: qs = qs.exclude(**fkw) elif show_active == dd.YesNo.yes: qs = qs.filter(**fkw) return qs @classmethod def quick_search_filter(model, search_text, prefix=''): q = Q() if search_text.isdigit(): for fn in model.quick_search_fields: kw = {prefix + fn.name + "__icontains": search_text} q = q | Q(**kw) return q #Skip referable's method return super(Referrable, model).quick_search_filter(search_text, prefix)
class Page(Hierarchical, Sequenced, Previewable, Publishable): class Meta: verbose_name = _("Node") verbose_name_plural = _("Nodes") unique_together = ["ref", "language"] publisher_location = "p" publisher_page_template = "pages/Node/default.pubpage.html" publisher_item_template = "pages/Node/default.pubitem.html" ref = models.CharField(_("Reference"), max_length=200, blank=True, null=True) language = LanguageField(default=models.NOT_PROVIDED, blank=True) title = dd.CharField(_("Title"), max_length=250, blank=True) # body = dd.BabelTextField(_("Body"), blank=True, format='plain') # raw_html = models.BooleanField(_("raw html"), default=False) @classmethod def lookup_page(cls, ref): try: return cls.objects.get(ref=ref, language=get_language()) except cls.DoesNotExist: pass @classmethod def get_dashboard_objects(cls, user): # print("20210114 get_dashboard_objects()", get_language()) qs = Page.objects.filter(parent__isnull=True, language=get_language()) for node in qs.order_by("seqno"): yield node def get_absolute_url(self, **kwargs): if self.ref: if self.ref != 'index': return dd.plugins.pages.build_plain_url(self.ref, **kwargs) return dd.plugins.pages.build_plain_url(**kwargs) def get_sidebar_caption(self): if self.title: return self.title if self.ref: return self.ref return str(self.id) #~ if self.ref or self.parent: #~ return self.ref #~ return unicode(_('Home')) def get_sidebar_item(self, request, other): kw = dict() add_user_language(kw, request) url = self.get_absolute_url(**kw) a = E.a(self.get_sidebar_caption(), href=url) if self == other: return E.li(a, **{'class': 'active'}) return E.li(a) def get_sidebar_html(self, request): items = [] #~ loop over top-level nodes for n in Page.objects.filter(parent__isnull=True).order_by('seqno'): #~ items += [li for li in n.get_sidebar_items(request,self)] items.append(n.get_sidebar_item(request, self)) if self.is_parented(n): children = [] for ch in n.children.order_by('seqno'): children.append(ch.get_sidebar_item(request, self)) if len(children): items.append(E.ul(*children, **{'class': 'nav nav-list'})) e = E.ul(*items, **{'class': 'nav nav-list'}) return tostring_pretty(e) def get_sidebar_menu(self, request): qs = Page.objects.filter(parent__isnull=True, language=get_language()) #~ qs = self.children.all() yield ('/', 'index', str(_('Home'))) #~ yield ('/downloads/', 'downloads', 'Downloads') #~ yield ('/about', 'about', 'About') #~ if qs is not None: for obj in qs.order_by("seqno"): if obj.ref and obj.title: yield ('/' + obj.ref, obj.ref, obj.title)
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).filter( primary=True): 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 Commit(Created, Authored): """A **Commit** is a git commit sha and other relevant data. .. attribute:: repository The repository where this change was committed. .. attribute:: url Url pointing to the details for this commit on Github. .. attribute:: git_user The Github user who commited this commit. If there is a user who was linked to this commit via user.User.github_username this will be blank. Otherwise it will contain the username of the unknown commiter .. attribute:: user User who commited this commit to github, uses user.User.github_username .. attribute:: sha Primary Key 40 Character sha1 hash for the commit .. attribute:: summary The summary of the commit. """ class Meta: app_label = 'github' verbose_name = _("Commit") verbose_name_plural = _('Commits') abstract = dd.is_abstract_model(__name__, 'Commit') repository = dd.ForeignKey(Repository, verbose_name=_("Repository"), related_name="commits") user = dd.ForeignKey('users.User', verbose_name=_("Author"), related_name="%(app_label)s_%(class)s_set_by_user", blank=True, null=True) ticket = dd.ForeignKey('tickets.Ticket', verbose_name=_("Ticket"), related_name="Commits", blank=True, null=True) git_user = dd.CharField(_("Github User Name"), blank=True, max_length=39, editable=False) commiter_name = dd.CharField( _("Git User Name"), blank=True, max_length=100, ) sha = dd.CharField( _("Sha Hash"), max_length=40, # primary_key=True, #Causes Issues with extjs6 unique=True, editable=False) url = dd.models.URLField(_("Commit page"), max_length=255, editable=False) description = dd.models.TextField( _("Description"), editable=False, blank=True, null=True, ) summary = dd.CharField(_("Summary"), editable=False, blank=True, null=True, max_length=100) comment = dd.CharField(_("Comment"), blank=True, null=True, max_length=255) unassignable = dd.models.BooleanField(_("Unassignable"), default=False, editable=True) data = dd.models.TextField(_("Raw json")) def get_overview_elems(self, ar): return [E.a(self.sha, href=self.url)] # @dd.displayfield(_("GH diff")) # def clickable_url(self, obj, ar): # return E.a(self.sha, href=self.url) @classmethod def from_api(cls, d, repo): """ :param d: dict representing the commit from the api :param repo: repo which this commit is from :return: Commit instance, without doing session lookup, just parses json return values and returns instance. """ try: c = Commit.objects.get(sha=d['sha']) id = c.id except Commit.DoesNotExist: id = None params = dict( id=id, repository=repo, user=None, ticket=None, git_user=d['committer']['login'] if d['committer'] is not None else "", commiter_name=d['commit']['committer']['name'], sha=d['sha'], url=d['html_url'], created=timezone.utc.localize( timezone.datetime.strptime(d['commit']['committer']['date'], "%Y-%m-%dT%H:%M:%SZ")), description=d['commit']['message'], summary=d['commit']['message'].split('\n', 1)[0][0:100], comment="", data=json.dumps(d), unassignable=False, ) return cls(**params)
class Repository(dd.Model): """ A **Repository** is a git username and repo name, along with an o-auth token to allow for more then 60 requests to github an hour. .. attribute:: user_name Github username. .. attribute:: repo_name Name of Repo belonging to user_name .. attribute:: o_auth Access token to be used with github's api, https://github.com/settings/tokens/new For public repos create a token with no scope. For private repos the token must have repo commit access, because github doesn't provide a way for a token to have read only status, we recommend not using this module with private repos unless you are sure that the token is secure. """ class Meta: app_label = 'github' verbose_name = _("Repository") verbose_name_plural = _('Repositories') abstract = dd.is_abstract_model(__name__, 'Repository') user_name = dd.CharField(_("User Name"), max_length=39) repo_name = dd.CharField(_("Repository Name"), max_length=100) o_auth = dd.CharField(_("OAuth Token"), max_length=40, blank=True) import_all_commits = Import_all_commits() import_new_commits = Import_new_commits() update_all_repos = Update_all_repos() def __str__(self): return "%s:%s" % (self.user_name, self.repo_name) @dd.displayfield(_("Url")) def url(self, ar): return "https://github.com/%s/%s/" % (self.user_name, self.repo_name) def api_url(self): return "https://api.github.com/repos/%s/%s/" % (self.user_name, self.repo_name) @dd.displayfield(_("Number Of commits")) def size(self, ar): return self.commits.count() def github_api_get_all_comments(self, sha=None): """ :return: yields json commits of comments for this repo's master branch untill none are left """ parms = {'page': 1, 'per_page': 100} if self.o_auth: parms['access_token'] = self.o_auth if sha is not None: parms['sha'] = sha r = requests.get(self.api_url() + 'commits', parms) content = json.loads(r.content.decode()) for c in content: yield c while 'rel="next"' in r.headers.get('link', ""): parms['page'] += 1 r = requests.get(self.api_url() + 'commits', parms) content = json.loads(r.content.decode()) for c in content: yield c def get_overview_elems(self, ar): return [E.a(self.repo_name, href=self.url)]
params = dict( id=id, repository=repo, user=None, ticket=None, git_user=d['committer']['login'] if d['committer'] is not None else "", commiter_name=d['commit']['committer']['name'], sha=d['sha'], url=d['html_url'], created=timezone.utc.localize( timezone.datetime.strptime(d['commit']['committer']['date'], "%Y-%m-%dT%H:%M:%SZ")), description=d['commit']['message'], summary=d['commit']['message'].split('\n', 1)[0][0:100], comment="", data=json.dumps(d), unassignable=False, ) return cls(**params) dd.inject_field("users.User", 'github_username', dd.CharField(_("Github Username"), max_length=39, blank=True)) @dd.schedule_often(3600) def update_all_repos(): Repository.update_all_repos.run_from_code( rt.models.github.Repositories.request())
class Address(AddressLocation): """Inherits fields from :class:`lino_xl.lib.countries.CountryRegionCity` (country, region, city. zip_code) and :class:`lino_xl.lib.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: app_label = 'addresses' 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 __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)
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)