Model can have a ForeignKey with one of the two models - django

I need some help with an issue that I am not able to resolve on my own. So in this model, a tenancy document can either have a ForeignKey with Building or Property.
We may have a tenancy agreement on the whole building or on a single property within that building. In the former case the tenancy documents are applied to the building, and in the latter case they only apply to a property.
I used content_types to add a generic foreign key but now I can’t figure out how to add autocomplete fields to contenttype and in the dropdown,
I just see building and property in admin form. I want to see building names and property names.
I learned about autocomplete fields in Django 2.0, it’s awesome but I don’t know how can I use something like that in this particular case or if there is a better way to do this?
models.py:
class TenancyDocument(models.Model):
KINDS = Choices('Tenancy Agreement', 'Stamp Duty', 'Inventory List')
id = FlaxId(primary_key=True)
kind = StatusField(choices_name='KINDS')
start_date = models.DateField(blank=True, null=True)
end_date = models.DateField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
content_type_limit = Q(
app_label='properties', model='building') | Q(
app_label='properties', model='property')
content_type = models.ForeignKey(
ContentType,
limit_choices_to=content_type_limit,
on_delete=models.CASCADE,
verbose_name='Lease type'
)
object_id = FlaxId(blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.kind
admin.py:
#admin.register(TenancyDocument)
class TenancyDocumentAdmin(admin.ModelAdmin):
list_display = ('id', 'kind', 'start_date', 'end_date','content_type')
list_filter = ('kind',)

It seems like the generic foreign key has always been more trouble than it's worth. It takes a simple concept, a relational join, and tries to make it clever, but then downstream packages like autocomplete won't work.
I ended up switching to two separate foreign keys, then added attributes to the class to pull fields from the correct related record.
class TenancyDocument(models.Model):
building = models.ForeignKey(Building, ondelete='CASCADE', null=True, blank=True)
prop = models.ForeignKey(Property, ondelete='CASCADE', null=True, blank=True)
def clean(self):
if not self.building and not self.prop:
raise ValidationError('Must provide either building or property.')
if self.building and self.prop:
raise ValidationError('Select building or property, but not both.')
super().clean()

Related

Filtering Django query filtering

I'm doing some querying currently and I was wondering if I would be able to query something from these 3 models where the return would give me all the projects the users are working on. I know about the basic filtering however thats not really enough in this case, how would one go about querying through 2 foreign keys.
class User(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email = models.EmailField()
class ProjectUser(models.Model):
project = models.ForeignKey("Project", on_delete=models.CASCADE)
user = models.ForeignKey("User", on_delete=models.CASCADE)
is_lead = models.BooleanField(default=False)
class Meta:
unique_together = (("project", "user"),)
class Project(models.Model):
name = models.CharField(max_length=255)
client = models.CharField(max_length=255)
complete = models.BooleanField(default=False)
You can obtain the Projects a user is working on with:
Project.objects.filter(
projectuser__user=user
)
The double underscores are used to look "through" relations. Furthermore the default related_query_name=… parameter [Django-doc] is, if not specified, the name of the model in lowercase.

Autocomplete with field related to ContentType

I need some help with an issue that I am not able to resolve on my own.
So in this model, a tenancy document can either have a ForeignKey with building or property.
I can't figure out how to add autocomplete fields to contenttype and in the dropdown, I just see building and property in admin form. I want to see building names and property names.
I learned about autocomplete fields in Django 2.0, it's awesome but I don't know how can I use something like that in this particular case or if there is a better way to do this?
Model:
class TenancyDocument(models.Model):
KINDS = Choices('Tenancy Agreement', 'Stamp Duty', 'Inventory List')
id = FlaxId(primary_key=True)
kind = StatusField(choices_name='KINDS')
start_date = models.DateField(blank=True, null=True)
end_date = models.DateField(blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
content_type_limit = Q(
app_label='properties', model='building') | Q(
app_label='properties', model='property')
content_type = models.ForeignKey(
ContentType,
limit_choices_to=content_type_limit,
on_delete=models.CASCADE,
verbose_name='Lease type'
)
object_id = FlaxId(blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.kind
Admin:
#admin.register(TenancyDocument)
class TenancyDocumnentAdmin(admin.ModelAdmin):
list_display = ('id', 'kind', 'start_date', 'end_date','content_type')
list_filter = ('kind',)

Django content type for both static and dynamic content

we're using Django 1.11 (soon moving to 2.0, so it's OK to answer for that as well).
We are solving the following problem:
We have a Ticket model with several fields, some of them are ForeignKey and some of them are ChoiceField.
We have a Condition model with which we want to target a specific value of some of the Ticket fields. If there were just ForeignKey fields in Ticket, we would use GenericRelation (ContentTypes). However there are also "static" values of ChoiceField.
One of our colleagues proposed a solution that for every Condition we create a hidden instance of Ticket that is never used anywhere else and is just there to hold the information about the ChoiceField value - and this Ticket is then linked to Condition through GenericRelation. But this sounds like a really bad idea.
I was thinking that many developers had to solve this. Is there any good and easy way how to do this?
Thank you.
EDIT: Concrete models code:
class Ticket(models.Model):
TYPE_RETURN = 'TYPE_RETURN'
TYPE_WARRANTY_CLAIM = 'TYPE_WARRANTY_CLAIM'
TYPES = (
(TYPE_RETURN, _('Return')),
(TYPE_WARRANTY_CLAIM, _('Warranty claim'))
)
TYPE_DEFAULT = TYPE_RETURN
state = models.ForeignKey(
to=TicketState,
verbose_name=_('State'),
null=True,
on_delete=models.SET_NULL
)
type = models.CharField(
max_length=20,
choices=TYPES,
default=TYPE_DEFAULT
)
(...)
class Condition(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.UUIDField(primary_key=False, editable=True, unique=False, blank=True, null=True)
content_object = GenericForeignKey('content_type', 'object_id')
(...)

Django: Converting a GenericForeignKey Relationship to a ForeignKey Relationship

I'm working to remove an existing GenericForeignKey relationship from some models. Id like to change it to the Reformatted Model below. Does migrations provide a way to convert the existing content_type and object_ids to the respective new ForeignKey's? (to keep existing data). Basically brand new at programming, so pardon me if I'm asking a stupid question.
class Donation(models.Model):
amount_id = models.CharField(max_length=12, unique=True, editable=False)
date_issued=models.DateField(auto_now_add=True)
description=models.TextField(blank=True, null=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type','object_id')
class Individual(BaseModel):
first_name = models.CharField(max_length=50)
middle_name = models.CharField(max_length=50, blank=True,
null=True)
last_name = models.CharField(max_length=50)
suffix = models.CharField(max_length=50, blank=True, null=True)
contributor = generic.GenericRelation(Donation, related_query_name='individual')
class Organization(models.Model):
name = models.CharField(max_length=100)
contributor = generic.GenericRelation(Donation, related_query_name='organization')
Reformatted Model
class Donation(models.Model):
amount_id = models.CharField(max_length=12, unique=True, editable=False)
date_issued=models.DateField(auto_now_add=True)
description=models.TextField(blank=True, null=True)
contributor_group = models.ForeignKey(Organization, null=True, blank=True, on_delete=models.CASCADE)
contributor_individual = models.ForeignKey(Individual, null=True, blank=True, on_delete=models
Based on your model definition of Donation Model, one of fields contributor_group , contributor_model will always be Null post migration.
I hope you have taken that into you consideration.
Just to be safe Do this in two phases.
1. Keep the content_type and object_id and add the two new fields.
2. Next step remove the generic fields post data population.
There are two ways to populate those new fields
Django migrations provides you a way to populate new fields with values during the migrations. You can look it up. I haven't done that before either.
For more control and some learning as well. You can populate that via scripting as well. You can setup django-extensions module in your project. And write a script to do that population for you as well. A sample script would look like.
from myproject.models import Donation, Individual, Organization
from django.contrib.contenttypes.models import ContentType
def run():
organization_content_type = ContentType.objects.get_for_model(Organization)
individual_content_type = ContentType.obejcts.get_for_model(Individual)
donations = Donation.objects.all()
for donation in donations:
if donation.content_type_id == organization_content_type.id:
donation.contributor_group = donation.object_id
elif donation.content_type_id == individual_content_type.id:
donation.contributor_individual = donation.object_id
else:
print "Can't identify content type for donation id {}".format(donation.id)
donation.save()
Check the values are correct and then remove the generic fields.
Facing some issues with formatting here.

django_filters picking up %(app_label)s_related_name as field name

(Django 1.8.14)
So I have one model, which uses a ContentType to point to a collections of models across separate apps.
I want to be able to "plug in" the functionality linking that model to the other models in the separate app. So we have the follwing:
class MyModelMixin(models.Model):
""" Mixin to get the TheModel data into a MyModel
"""
updated_date = models.DateTimeField(null=True, blank=True)
submission = GenericRelation(
TheModel,
related_query_name='%(app_label)s_the_related_name')
class Meta:
abstract = True
The main model model looks like this:
class TheModel(models.Model):
""" This tracks a specific submission.
"""
updated = models.DateTimeField()
status = models.CharField(max_length=32, blank=True, null=True)
final_score = models.DecimalField(
decimal_places=2, max_digits=30, default=-1,
)
config = models.ForeignKey(Config, blank=True, null=True)
content_type = models.ForeignKey(
ContentType,
blank=True,
null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
my_model_instance = GenericForeignKey()
And the mixin is included in the MyModel models, which are available in many different apps.
When using a filter for this, using django filters, there is an issue. I have a filter that's supposed to be instantiated to each app when it's used. However when I instantiate, for example, with
class MyFilterSet(Filterset):
def __init__(self, *args, **kwargs):
self.config_pk = kwargs.pop('config_pk', None)
if not self.config_pk:
return
self.config = models.Config.objects.get(pk=self.config_pk)
self.custom_ordering["c_name"] =\
"field_one__{}_the_related_name__name".format(
self.config.app_model.app_label,
)
super(MyFilterSet,self).__init__(*args, **kwargs)
However, when I use this, I get
FieldError: Cannot resolve keyword 'my_app_the_related_name field. Choices are: %(app_label)s_the_related_name, answers, config, config_id, content_type, content_type_id, final_score, form, form_id, id, object_id, section_scores, status, updated
How can %(app_label)s_the_related_name be in the set of fields, and how can I either make it render properly (like it does outside of django filters) or is there some other solution.
You have probably encountered issue #25354. Templating related_query_name on GenericRelation doesn't work properly on Django 1.9 or below.
Added in Django 1.10 was related_query_name now supports app label and class interpolation using the '%(app_label)s' and '%(class)s' strings, after the fix was merged.