def test_can_override_formfield(self): model_field = JsonField() class FakeFieldClass(object): def __init__(self, *args, **kwargs): pass form_field = model_field.formfield(form_class=FakeFieldClass) self.assertIsInstance(form_field, FakeFieldClass)
class WebhookLog(models.Model): webhook = models.ForeignKey(Webhook, null=False, blank=False, related_name="logs") url = models.URLField(null=False, blank=False, verbose_name=_("URL")) status = models.IntegerField(null=False, blank=False, verbose_name=_("status code")) request_data = JsonField(null=False, blank=False, verbose_name=_("request data")) request_headers = JsonField(null=False, blank=False, verbose_name=_("request headers"), default={}) response_data = models.TextField(null=False, blank=False, verbose_name=_("response data")) response_headers = JsonField(null=False, blank=False, verbose_name=_("response headers"), default={}) duration = models.FloatField(null=False, blank=False, verbose_name=_("duration"), default=0) created = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created', '-id']
class Version(models.Model): key = models.ForeignKey(Document) source = models.CharField(max_length=255) docID = models.TextField() timestamps = JsonField() providerUpdatedDateTime = models.DateTimeField(null=True) raw = JsonField() normalized = JsonField(null=True) status = models.TextField(null=True)
class IndicatorRecord(models.Model): record_choices = tuple((rt.name, rt.title) for rt in RecordType) source_choices = tuple((rs.name, rs.title) for rs in RecordSource) record_type = models.CharField(max_length=2, choices=record_choices) created = models.DateTimeField(auto_now_add=True, editable=False) modified = models.DateTimeField(auto_now=True) info = JsonField() info_source = models.CharField(max_length=3, choices=source_choices) info_hash = models.CharField(max_length=40) info_date = models.DateTimeField() objects = IndicatorManager() class Meta: unique_together = (("info_hash", "info_source", "info_date"), ) def generate_hash(self): info_pickle = pickle.dumps(self.info) info_sha1 = hashlib.sha1(info_pickle).hexdigest() return info_sha1 def save(self, *args, **kwargs): if not self.info_hash: self.info_hash = self.generate_hash() super(IndicatorRecord, self).save(*args, **kwargs)
class Payment(BaseModel): PAYMENT_STATUS_CHOICES = ( ('p', 'Pending'), ('r', 'Received'), ('c', 'Cancelled'), ('f', 'Refunded') ) gateway = models.ForeignKey("PaymentGateway") user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True) ptype = models.ForeignKey("PaymentType") payment_id = models.CharField( "Payment ID", max_length=50, blank=True, null=True ) amount = models.FloatField(default=0.0) status = models.CharField( "Status", max_length=1, choices=PAYMENT_STATUS_CHOICES ) status_pg = models.CharField( "Status from Payment Gateway", max_length=50 ) # store the whole json response raw_details = JsonField(blank=True, null=True) def __str__(self): return self.payment_id
class AuditLog(TimeStampedModel): """An audit log of events and notes. Inherits ``created`` and ``modified`` from TimeStampedModel """ user = models.ForeignKey(USER_MODEL) # Generic Foreign Key next three fields content_type = models.ForeignKey(ContentType, null=True, blank=True) object_id = models.PositiveIntegerField(null=True) content_object = generic.GenericForeignKey('content_type', 'object_id') audit_type = models.IntegerField(default=LOG, choices=AUDIT_CHOICES) action = models.CharField( max_length=128, null=True, blank=True, help_text='method triggering audit', db_index=True, ) action_response = JsonField(default={}, help_text='HTTP response from action') action_note = models.TextField( blank=True, null=True, help_text='either the note text or a description of the action', ) organization = models.ForeignKey(Organization, related_name='audit_logs') class Meta: ordering = ('-created', ) objects = AuditLogManager() def __unicode__(self): return u'{0} <{1}> ({2})'.format(self.get_audit_type_display(), self.user, self.pk) def save(self, *args, **kwargs): """Ensure that only notes are saved""" if self.audit_type != NOTE and self.pk: raise PermissionDenied('AuditLogs cannot be edited, only notes') return super(AuditLog, self).save(*args, **kwargs) def to_dict(self): """serializes an audit_log""" # avoid cyclical import from seed.models import obj_to_dict log_dict = obj_to_dict(self) log_dict['audit_type'] = self.get_audit_type_display() log_dict['user'] = { 'first_name': self.user.first_name, 'last_name': self.user.last_name, 'email': self.user.email, 'id': self.user.pk, } log_dict['organization'] = { 'id': self.organization.pk, 'name': self.organization.name, } log_dict['content_type'] = self.content_object._meta.model_name return log_dict
class UserProfile(models.Model): image = models.ImageField(upload_to='profile', null=False, blank=False, default="profile/default.jpg") birth_date = models.DateField(blank=True, null=True) user = models.OneToOneField(User, unique=True, related_name='profile') privacy_settings = JsonField(default=CONFIG_PRIVACY) #interest_groups = models.ManyToManyField(InterestGroup, related_name='members', null=True) def __str__(self): return 'profile ' + self.user.username def __init__(self, *args, **kwargs): super(UserProfile, self).__init__(*args, **kwargs) self.privacy_settings_old = self.privacy_settings def change_privacity(self): if self.privacy_settings.get('show_address', True) == self.privacy_settings_old.get('show_address', True): return False else: return True def get_can_show_location(self): return self.privacy_settings.get('show_address', True) def save(self, *args, **kwargs): obj = super(UserProfile, self).save(*args, **kwargs) if self.change_privacity(): self.locations.filter(is_address=True).first().save() return obj
class CreditRule(TranslatedModelFallbackMixin, TranslatableModel, UUIDModel): transaction_type = models.SmallIntegerField( choices=CreditTransactionType.choices) type = models.CharField(max_length=30) name = models.CharField(max_length=50) description = models.CharField(max_length=250, blank=True, default='') options = JsonField(default=dict, blank=True) is_active = models.BooleanField(default=True) translations = TranslatedFields( _local_name=models.CharField(max_length=50, blank=True, default=''), _local_description=models.CharField(max_length=250, blank=True, default='')) def __init__(self, *args, **kwargs): super(CreditRule, self).__init__(*args, **kwargs) if self.type: self.__class__ = CREDIT_RULES.get(self.type, CreditRule) def __unicode__(self): return "%s:%s:%s" % (self.get_transaction_type_display(), self.type, self.name) def display(self, transaction): raise NotImplementedError()
class CertificateMonitor(IndicatorLookupBase): """ A lookup monitor for certificate indicators. This class extends IndicatorLookupBase, adding the field 'certificate_value' for the indicator value as a primary key. As with all indicator lookups, the combination of indicator value and owner must be unique. """ certificate_value = models.TextField(primary_key=True) """The certificate fragment to be monitored""" resolutions = JsonField() """ The full resolutions. Here is the basic structure of this field: { <ip>: { "geo_location": <location>, "country": <code>, "domains": [ <domain>, ...] } } """ # Commented out by LNguyen on 1/24/2017 - Certificate Monitor will not require owner and certificate value to be a unique key class Meta: """ A metaclass for Certificate Monitor that specifies that the combination of 'owner' (the person submitting the monitor) and 'certificate_value' (the indicator value) must be unique. """ unique_together = (('owner', 'certificate_value'), )
class StorageEntry(models.Model): owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, related_name="storage_entries", verbose_name=_("owner")) created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False, verbose_name=_("created date")) modified_date = models.DateTimeField(auto_now=True, null=False, blank=False, verbose_name=_("modified date")) key = models.CharField(max_length=255, null=False, blank=False, verbose_name=_("key")) value = JsonField(blank=True, default=None, null=True, verbose_name=_("value")) class Meta: verbose_name = "storage entry" verbose_name_plural = "storages entries" unique_together = ("owner", "key") ordering = ["owner", "key"]
class AuthData(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="auth_data") key = models.SlugField(max_length=50) value = models.CharField(max_length=300) extra = JsonField() class Meta: unique_together = ["key", "value"]
class AbstractCustomAttributesValues(OCCModelMixin, models.Model): attributes_values = JsonField(null=False, blank=False, default={}, verbose_name=_("values")) class Meta: abstract = True ordering = ["id"]
class ProjectModulesConfig(models.Model): project = models.OneToOneField("Project", null=False, blank=False, related_name="modules_config", verbose_name=_("project")) config = JsonField(null=True, blank=True, verbose_name=_("modules config")) class Meta: verbose_name = "project modules config" verbose_name_plural = "project modules configs" ordering = ["project"]
class XMLLinkCRMSource(CRMSource): url = models.URLField() mapping = JsonField(blank=False) crm_shouts = GenericRelation('shoutit_crm.XMLCRMShout', related_query_name='xml_link_crm_source') def __unicode__(self): return "%s: %s @ %s" % (self.pk, self.url, self.provider)
class ExternalSessions(models.Model): """ External cookie sessions for scrapers """ # Note: Yes, this syntax is mildly awkward, but it allows for very easy addition of additional sources in the list # at the end of the line service_choices = tuple( (rs.name, rs.title) for rs in RecordSource if rs in [RecordSource.IID]) service = models.CharField(max_length=3, choices=service_choices) cookie = JsonField()
class Document(models.Model): key = models.TextField(primary_key=True) source = models.CharField(max_length=255) docID = models.TextField() providerUpdatedDateTime = models.DateTimeField(null=True) raw = JsonField() timestamps = JsonField(null=True) normalized = JsonField(null=True) status = models.TextField(null=True) def save(self, *args, **kwargs): if not self.key: self.key = self._make_key(self.source, self.docID) return super(Document, self).save(*args, **kwargs) @classmethod def _make_key(cls, source, docID): return '|'.join((source, docID))
class CustomBuildingHeaders(models.Model): """Specify custom building header mapping for display.""" super_organization = models.ForeignKey(SuperOrganization, blank=True, null=True, verbose_name=_('SeedOrg'), related_name='custom_headers') # 'existing, normalized name' -> 'preferred display name' # e.g. {'district': 'Boro'} building_headers = JsonField(default={}) objects = JsonManager()
class Timeline(models.Model): content_type = models.ForeignKey(ContentType, related_name="content_type_timelines") object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') namespace = models.CharField(max_length=250, default="default", db_index=True) event_type = models.CharField(max_length=250, db_index=True) project = models.ForeignKey(Project, null=True) data = JsonField() data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") created = models.DateTimeField(default=timezone.now) class Meta: index_together = [('content_type', 'object_id', 'namespace'), ]
class LearningRecord(models.Model): xapi = JsonField() course_code = models.CharField(max_length=5000, blank=False) platform = models.CharField(max_length=5000, blank=False) verb = models.CharField(max_length=5000, blank=False) username = models.CharField(max_length=5000, blank=True) platformid = models.CharField(max_length=5000, blank=True) platformparentid = models.CharField(max_length=5000, blank=True) parentusername = models.CharField(max_length=5000, blank=True) parentdisplayname = models.CharField(max_length=5000, blank=True) message = models.TextField(blank=True) #mentions = models.TextField(blank=True) datetimestamp = models.DateTimeField(blank=True, null=True) senttolrs = models.CharField(max_length=5000, blank=True)
class Timeline(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') namespace = models.SlugField(default="default") event_type = models.SlugField() data = JsonField() created = models.DateTimeField(default=timezone.now) def save(self, *args, **kwargs): if self.id: raise ValidationError("Not modify allowed for timeline entries") return super().save(*args, **kwargs) class Meta: index_together = [('content_type', 'object_id', 'namespace'), ]
class Timeline(models.Model): content_type = models.ForeignKey(ContentType, related_name="content_type_timelines") object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') namespace = models.CharField(max_length=250, default="default", db_index=True) event_type = models.CharField(max_length=250, db_index=True) project = models.ForeignKey(Project) data = JsonField() data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") created = models.DateTimeField(default=timezone.now) def save(self, *args, **kwargs): if self.id: raise ValidationError("Not modify allowed for timeline entries") return super().save(*args, **kwargs) class Meta: index_together = [('content_type', 'object_id', 'namespace'), ]
class Store(models.Model): logo = models.ImageField(upload_to='logo', null=False, blank=True) name = models.CharField(max_length=255, blank=True) #slug = AutoSlugField(populate_from='name', always_update=True, unique=True) slogan = models.TextField() style = JsonField(default=STYLE_STORE) profile = models.OneToOneField(UserProfile, unique=True, related_name='store') status = models.IntegerField(choices=STATUS_STORE, default=0) slug = models.SlugField(auto_created=True) def __str__(self): return self.name def clean_slug(self): if self.name != '': if Store.objects.get(slug= self.slug).count() > 0: raise ValidationError('Error, fields name is unique') def get_url(self): if self.slug: return reverse('store', args=[self.slug]) else: return '' def save(self, *args, **kwargs): # Validate if has all config for key, value in STYLE_STORE.items(): if not key in self.style or not self.style[key]: self.style[key] = value if self.name and not self.slug: from django.template.defaultfilters import slugify slug = slugify(self.name) while Store.objects.filter(slug=slug).count() != 0: slug += '1' self.slug = slug if self.name != '': self.status = 1 else: self.status = 0 return super(Store, self).save(*args, **kwargs)
class UserLocation(models.Model): title = models.CharField(max_length=100) userProfile = models.ForeignKey(UserProfile, related_name='locations') lat = models.FloatField(null=False) lng = models.FloatField(null=False) radius = models.IntegerField(default=5000) is_address = models.BooleanField(default=False) address = JsonField(default=INFO_ADDRESS) def get_address(self): address = self.address address['lat'] = self.lat address['lng'] = self.lng return address def save(self, *args, **kwargs): try: url = 'http://maps.googleapis.com/maps/api/geocode/json?latlng='+ \ str(self.lat) + ',' + str(self.lng) +'&sensor=true' response = urllib.request.urlopen(url) json_response = response.read() obj = json.loads(json_response.decode("utf-8")) if len(obj['results']): for district in obj["results"][0]["address_components"]: if "locality" in district["types"]: self.address['locality'] = district["long_name"] elif "administrative_area_level_2" in district["types"]: self.address['administrative_area_level_2'] = district["long_name"] elif "administrative_area_level_1" in district["types"]: self.address['administrative_area_level_1'] = district["long_name"] elif "country" in district["types"]: self.address['country'] = district["long_name"] except: pass return super(UserLocation, self).save(*args, **kwargs) def __str__(self): return self.title
class CreditTransaction(UUIDModel): user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='credit_transactions') amount = models.IntegerField() rule = models.ForeignKey(CreditRule, related_name='transactions') properties = JsonField(default=dict, blank=True) def __unicode__(self): return "%s %d:%s by %s" % (self.id, self.amount, self.rule, self.user.username) @property def type(self): return CREDIT_IN if self.amount > 0 else CREDIT_OUT def get_type_display(self): return str(self.type) def display(self): return self.rule.display(self) @property def app_url(self): self.display() return getattr(self.target, 'app_url', None) if hasattr( self, 'target') else None @property def web_url(self): self.display() return getattr(self.target, 'web_url', None) if hasattr( self, 'target') else None def notify_user(self): from shoutit.controllers.notifications_controller import notify_user_of_credit_transaction notify_user_of_credit_transaction(self) def serializer(self, version=None): from shoutit_credit.serializers import CreditTransactionSerializer return CreditTransactionSerializer
class Notification(models.Model): # TODO: Validar que el usuario o el email sea requerido (uno si o si) type = models.CharField(max_length=20, choices=TYPE_NOTIFICATION) message = models.TextField() created = models.DateTimeField(auto_now_add=True) receiver = models.ForeignKey(User, null=True) extras = JsonField() read = models.DateTimeField(blank=True, null=True) objects = NotificationManager.as_manager() def marked_read(self): if not self.read: self.read = datetime_now() self.save() def __str__(self): return self.message class Meta: verbose_name = "Notification" verbose_name_plural = "Notifications" ordering = ["-created"] def get_user(self): return self.extras.get('user', "") def get_object_id(self): return self.extras.get('id', 0) def get_url(self): return self.extras.get('url', "") def get_email(self): if self.receiver and self.receiver.email: return self.receiver.email elif self.extras.get('email'): return self.extras.get('email') else: return None
class ConfigNotification(models.Model): user = models.OneToOneField(AUTH_USER_MODEL, related_name='config_notifications', unique=True) config = JsonField(default=CONFIG_NOTIFICATION) def save(self, *args, **kwargs): # Validate if has all config for check_key in CONFIG_NOTIFICATION.keys(): if not self.config.get(check_key): self.config[check_key] = CONFIG_NOTIFICATION[check_key] for config in CONFIG_NOTIFICATION.keys(): for key, value in CONFIG_NOTIFICATION[config].items(): if not self.config.get(config) or not key in self.config.get(config) or key == 'label': self.config[config][key] = value super(ConfigNotification, self).save(*args, **kwargs) def has_perm(self, type, canal): if self.config.get(type) and self.config[type].get(canal): return True else: return False
class Location(models.Model): """Geo and meta representation of a location""" name = models.CharField(_('Location Name'), max_length=255) address = models.CharField(_('Address'), max_length=255, blank=True, null=True, unique=True) location = models.PointField(_('Location'), blank=True, null=True) location_hash = models.CharField(max_length=32, null=True) meta = JsonField(_('metadata'), blank=True, null=True) objects = models.GeoManager() def __str__(self): return "{name}".format(name=self.name) def save(self, **kwargs): location_hash = md5('{}@{}'.format( self.name, self.address).encode('utf-8')).hexdigest() if self.location_hash != location_hash and self.address: location_geocode = geocode(self.address) if location_geocode: self.meta = location_geocode.raw pos = [float(self.meta.get(coord)) for coord in ['lon', 'lat']] self.location = Point(*pos) self.location_hash = location_hash else: self.meta = {"geocoded": False} self.location = None super().save(**kwargs)
class SEEDUser(AbstractBaseUser, PermissionsMixin): """ An abstract base class implementing a fully featured User model with admin-compliant permissions. Username, password and email are required. Other fields are optional. """ username = models.EmailField( _('username (email)'), unique=True, help_text=_('User\'s email address. Used for auth as well.')) first_name = models.CharField(_('first name'), max_length=30, blank=True) last_name = models.CharField(_('last name'), max_length=30, blank=True) email = models.EmailField(_('email address'), blank=True) is_staff = models.BooleanField( _('staff status'), default=False, help_text=_('Designates whether the user can log into this admin ' 'site.')) is_active = models.BooleanField( _('active'), default=True, help_text=_('Designates whether this user should be treated as ' 'active. Unselect this instead of deleting accounts.')) date_joined = models.DateTimeField(_('date joined'), default=timezone.now) default_custom_columns = JsonField(default={}) default_building_detail_custom_columns = JsonField(default={}) show_shared_buildings = models.BooleanField( _('active'), default=False, help_text=_('shows shared buildings within search results')) default_organization = models.ForeignKey( Organization, blank=True, null=True, related_name='default_users' ) api_key = models.CharField( _('api key'), max_length=128, blank=True, default='', db_index=True ) objects = UserManager() USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] class Meta: verbose_name = _('user') verbose_name_plural = _('users') def get_absolute_url(self): return "/users/%s/" % urlquote(self.username) def get_full_name(self): """ Returns the first_name plus the last_name, with a space in between. """ full_name = '%s %s' % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): "Returns the short name for the user." return self.first_name def email_user(self, subject, message, from_email=None): """ Sends an email to this User. """ send_mail(subject, message, from_email, [self.email]) def generate_key(self): """ Creates and sets an API key for this user. Adapted from tastypie: https://github.com/toastdriven/django-tastypie/blob/master/tastypie/models.py#L47 # noqa """ new_uuid = uuid.uuid4() api_key = hmac.new(new_uuid.bytes, digestmod=sha1).hexdigest() self.api_key = api_key self.save() def save(self, *args, **kwargs): """ Ensure that email and username are synced. """ # NL: Why are we setting the email to the user name, don't we need the # email? It seems that the username is then suppose to be the email, # correct? Regardless, this code seems problematic if self.email.lower() != self.username: self.email = self.username return super(SEEDUser, self).save(*args, **kwargs)
class HistoryEntry(models.Model): """ Domain model that represents a history entry storage table. It is used for store object changes and comments. """ id = models.CharField(primary_key=True, max_length=255, unique=True, editable=False, default=_generate_uuid) user = JsonField(blank=True, default=None, null=True) created_at = models.DateTimeField(default=timezone.now) type = models.SmallIntegerField(choices=HISTORY_TYPE_CHOICES) key = models.CharField(max_length=255, null=True, default=None, blank=True, db_index=True) # Stores the last diff diff = JsonField(null=True, default=None) # Stores the last complete frozen object snapshot snapshot = JsonField(null=True, default=None) # Stores a values of all identifiers used in values = JsonField(null=True, default=None) # Stores a comment comment = models.TextField(blank=True) comment_html = models.TextField(blank=True) delete_comment_date = models.DateTimeField(null=True, blank=True, default=None) delete_comment_user = JsonField(blank=True, default=None, null=True) # Flag for mark some history entries as # hidden. Hidden history entries are important # for save but not important to preview. # Order fields are the good example of this fields. is_hidden = models.BooleanField(default=False) # Flag for mark some history entries as complete # snapshot. The rest are partial snapshot. is_snapshot = models.BooleanField(default=False) _importing = None @cached_property def is_change(self): return self.type == HistoryType.change @cached_property def is_create(self): return self.type == HistoryType.create @cached_property def is_delete(self): return self.type == HistoryType.delete @cached_property def owner(self): pk = self.user["pk"] model = apps.get_model("users", "User") return model.objects.get(pk=pk) @cached_property def values_diff(self): result = {} users_keys = ["assigned_to", "owner"] def resolve_diff_value(key): value = None diff = get_diff_of_htmls( self.diff[key][0] or "", self.diff[key][1] or "" ) if diff: key = "{}_diff".format(key) value = (None, diff) return (key, value) def resolve_value(field, key): data = self.values[field] key = str(key) if key not in data: return None return data[key] for key in self.diff: value = None # Note: Hack to prevent description_diff propagation # on old HistoryEntry objects. if key == "description_diff": continue elif key == "content_diff": continue elif key == "blocked_note_diff": continue elif key in["description", "content", "blocked_note"]: (key, value) = resolve_diff_value(key) elif key in users_keys: value = [resolve_value("users", x) for x in self.diff[key]] elif key == "watchers": value = [[resolve_value("users", x) for x in self.diff[key][0]], [resolve_value("users", x) for x in self.diff[key][1]]] elif key == "points": points = {} pointsold = self.diff["points"][0] pointsnew = self.diff["points"][1] # pointsold = pointsnew if pointsold is None: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) points[role_name] = [None, resolve_value("points", point_id)] else: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) oldpoint_id = pointsold.get(role_id, None) points[role_name] = [resolve_value("points", oldpoint_id), resolve_value("points", point_id)] # Process that removes points entries with # duplicate value. for role in dict(points): values = points[role] if values[1] == values[0]: del points[role] if points: value = points elif key == "attachments": attachments = { "new": [], "changed": [], "deleted": [], } oldattachs = {x["id"]:x for x in self.diff["attachments"][0]} newattachs = {x["id"]:x for x in self.diff["attachments"][1]} for aid in set(tuple(oldattachs.keys()) + tuple(newattachs.keys())): if aid in oldattachs and aid in newattachs: changes = make_diff_from_dicts(oldattachs[aid], newattachs[aid], excluded_keys=("filename", "url")) if changes: change = { "filename": newattachs.get(aid, {}).get("filename", ""), "url": newattachs.get(aid, {}).get("url", ""), "changes": changes } attachments["changed"].append(change) elif aid in oldattachs and aid not in newattachs: attachments["deleted"].append(oldattachs[aid]) elif aid not in oldattachs and aid in newattachs: attachments["new"].append(newattachs[aid]) if attachments["new"] or attachments["changed"] or attachments["deleted"]: value = attachments elif key == "custom_attributes": custom_attributes = { "new": [], "changed": [], "deleted": [], } oldcustattrs = {x["id"]:x for x in self.diff["custom_attributes"][0] or []} newcustattrs = {x["id"]:x for x in self.diff["custom_attributes"][1] or []} for aid in set(tuple(oldcustattrs.keys()) + tuple(newcustattrs.keys())): if aid in oldcustattrs and aid in newcustattrs: changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid], excluded_keys=("name")) if changes: change = { "name": newcustattrs.get(aid, {}).get("name", ""), "changes": changes } custom_attributes["changed"].append(change) elif aid in oldcustattrs and aid not in newcustattrs: custom_attributes["deleted"].append(oldcustattrs[aid]) elif aid not in oldcustattrs and aid in newcustattrs: custom_attributes["new"].append(newcustattrs[aid]) if custom_attributes["new"] or custom_attributes["changed"] or custom_attributes["deleted"]: value = custom_attributes elif key in self.values: value = [resolve_value(key, x) for x in self.diff[key]] else: value = self.diff[key] if not value: continue result[key] = value return result class Meta: ordering = ["created_at"]
class TaxLotState(models.Model): # The state field names should match pretty close to the pdf, just # because these are the most 'public' fields in terms of # communicating with the cities. confidence = models.FloatField(default=0, null=True, blank=True) # Support finding the property by the import_file import_file = models.ForeignKey(ImportFile, null=True, blank=True) # Add organization to the tax lot states organization = models.ForeignKey(Organization) data_state = models.IntegerField(choices=DATA_STATE, default=DATA_STATE_UNKNOWN) merge_state = models.IntegerField(choices=MERGE_STATE, default=MERGE_STATE_UNKNOWN, null=True) custom_id_1 = models.CharField(max_length=255, null=True, blank=True) jurisdiction_tax_lot_id = models.CharField(max_length=2047, null=True, blank=True) block_number = models.CharField(max_length=255, null=True, blank=True) district = models.CharField(max_length=255, null=True, blank=True) address_line_1 = models.CharField(max_length=255, null=True, blank=True) address_line_2 = models.CharField(max_length=255, null=True, blank=True) normalized_address = models.CharField(max_length=255, null=True, blank=True, editable=False) city = models.CharField(max_length=255, null=True, blank=True) state = models.CharField(max_length=255, null=True, blank=True) postal_code = models.CharField(max_length=255, null=True, blank=True) number_properties = models.IntegerField(null=True, blank=True) extra_data = JsonField(default={}, blank=True) def __unicode__(self): return u'TaxLot State - %s' % self.pk def promote(self, cycle): """ Promote the TaxLotState to the view table for the given cycle Args: cycle: Cycle to assign the view Returns: The resulting TaxLotView (note that it is not returning the TaxLotState) """ # First check if the cycle and the PropertyState already have a view tlvs = TaxLotView.objects.filter(cycle=cycle, state=self) if len(tlvs) == 0: _log.debug("Found 0 TaxLotViews, adding TaxLot, promoting") # There are no PropertyViews for this property state and cycle. # Most likely there is nothing to match right now, so just # promote it to the view # Need to create a property for this state if self.organization is None: _log.error("organization is None") taxlot = TaxLot.objects.create(organization=self.organization) tlv = TaxLotView.objects.create(taxlot=taxlot, cycle=cycle, state=self) # This is legacy but still needed here to have the tests pass. self.data_state = DATA_STATE_MATCHING self.save() return tlv elif len(tlvs) == 1: _log.debug("Found 1 PropertyView... Nothing to do") # PropertyView already exists for cycle and state. Nothing to do. return tlvs[0] else: _log.debug("Found %s PropertyView" % len(tlvs)) _log.debug("This should never occur, famous last words?") return None def to_dict(self, fields=None, include_related_data=True): """ Returns a dict version of the TaxLotState, either with all fields or masked to just those requested. """ # TODO: make this a serializer and/or merge with PropertyState.to_dict if fields: model_fields, ed_fields = split_model_fields(self, fields) extra_data = self.extra_data ed_fields = filter(lambda f: f in extra_data, ed_fields) result = {field: getattr(self, field) for field in model_fields} result['extra_data'] = { field: extra_data[field] for field in ed_fields } # always return id's and canonical_building id's result['id'] = result['pk'] = self.pk # should probably also return children, parents, and coparent # result['children'] = map(lambda c: c.id, self.children.all()) # result['parents'] = map(lambda p: p.id, self.parents.all()) # result['co_parent'] = (self.co_parent and self.co_parent.pk) # result['coparent'] = (self.co_parent and { # field: self.co_parent.pk for field in ['pk', 'id'] # }) return result d = obj_to_dict(self, include_m2m=include_related_data) # if include_related_data: # d['parents'] = list(self.parents.values_list('id', flat=True)) # d['co_parent'] = self.co_parent.pk if self.co_parent else None return d def save(self, *args, **kwargs): # Calculate and save the normalized address if self.address_line_1 is not None: self.normalized_address = normalize_address_str( self.address_line_1) else: self.normalized_address = None return super(TaxLotState, self).save(*args, **kwargs)