Django model creation jointly unique fields - python

I'm trying to create a model for Django that looks like this:
class Device(Model):
UDID = CharField(length=64, primary_key=True)
# more irrelevant stuff
class DeviceProperty(Model):
device = ForeignKey(Device)
name = CharField(length=255)
value = CharField(length=255)
readOnly = BooleanField()
But then, for data-integrity reasons, a single device shouldn't have two properties with the same name. So I would need to make the device and name fields of DeviceProperty jointly unique.
A simple way to achieve this would be by having a composite primary key on the two fields, but this raises a few issues, and more importantly isn't supported by Django.
I haven't found anything about this in the Django documentation. Have I missed anything?

unique_together is what you want.
class DeviceProperty(Model):
…
class Meta:
unique_together = ['device', 'name']

Related

How to override M2M field names and models in Django with an existing database?

I'm using Django 3.1.3 and working with an existing postgresql database. Most of the models and fields names of this DB are badly chosen and/or way too long. Most of the time its easy to change them with some handy Django options like so :
class NewModelName(models.Models):
new_field_name = models.CharField(max_length=50, db_column='old_field_name')
class Meta:
managed=False
db_table='database_old_table_name'
But let say I want to change a M2M field name and the corresponding model name. I'd like to have something like :
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField('RelatedModel', blank=True, db_column='old_m2m_field_name')
class Meta:
managed=False
db_table='foo_old_table_name'
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed=False
db_table='related_model_old_table_name'
But if I do that, Django will throw an error stating
django.db.utils.ProgrammingError: relation "foo_new_m2m_field_name" does not exist. It is like it is ignoring the db_column option. Any idea how I could get to a similar result ?
Thanks!
From Django documentation regarding ManyToManyField
ManyToManyField.db_table The name of the table to create for storing
the many-to-many data. If this is not provided, Django will assume a
default name based upon the names of: the table for the model defining
the relationship and the name of the field itself.
Also depending on column names (non standard names) in original database you might have to define through model ( pivot table) as through table
You will probably need to manually define the Through model (that Django would otherwise implicitly create behind the scenes) in order to make it unmanaged.
class Foo(models.Models):
new_m2m_field_name = models.ManyToManyField(
"RelatedModel",
blank=True,
db_column="old_m2m_field_name",
through="FooRelatedJoin", # <- new
)
class Meta:
managed = False
db_table = "foo_old_table_name"
class RelatedModel(models.Models):
name = models.CharField(max_length=50)
class Meta:
managed = False
db_table = "related_model_old_table_name"
class FooRelatedJoin(models.Models): # <- all new
foo = models.ForeignKey(Foo)
related_model = models.ForeignKey(RelatedModel)
class Meta:
managed = False
db_table = "foo_join_table"
You could add a property db_table (link)
linking to the previous table, named foo_old_table_name in your case.
According to the doc,
By default, this table name is generated using the name of the
many-to-many field and the name of the table for the model that
contains it
So for the field new_m2m_field_name, the previous table making the link was named : old_field_name_database_old_table_name.
Hence :
new_field_name = models.CharField(max_length=50, db_column='old_field_name', db_table='old_field_name_database_old_table_name')
The option through could be changed too, but I do not think it is necessary if the modifications on names are coherent.

Django Rest Framework: source / re-name without re-confirming the field settings

I'd like to change many fields name in DRF ModelSerializer without the need to re-typing the fields.
According a post on SO (ref), one can re-name a field name within the serializer by using source, such as:
newName = serializers.CharField(source='old_name')
However, this method takes away the benefits of using a ModelSerializer as you essentially do the work twice. This become heavy when you have many fields adhering to one internal naming convention but want to display another naming convention within the API.
in my case, I have a model field such as:
product_uid = models.UUIDField(primary_key=False, unique=True, default=uuid.uuid4, editable=False)
In the API, I'd like the field to be called 'uid'.
If I would do the following:
uid = serializers.UUIDField(source=product_uid)
would result in editable=True
Is there a way to reference to a ModelField and keep its definition intact according the Model (as you normally do when using serializers.ModelSerializer) but only change the name, e.g. something like: uid = serializers.ModelField(source=product_uid) ?
If you want your field not to be editable, you can use the read_only parameter (https://www.django-rest-framework.org/api-guide/fields/#read_only)
You can try:
uid = serializers.UUIDField(source=product_uid, read_only=True)
You can also use the ModelSerializer using extra_kwargs :
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['product_uid', 'field_a', 'field_b']
extra_kwargs = {'product_uid': {'source': 'uid'}} # Add `'read_only': True` if needed
If you have many fields, you can generate extra_kwargs programmatically

How can I resolve custom fields for django models using django_graphene?

Looking at graphene_django, I see they have a bunch of resolvers picking up django model fields mapping them to graphene types.
I have a subclass of JSONField I'd also like to be picked up.
:
# models
class Recipe(models.Model):
name = models.CharField(max_length=100)
instructions = models.TextField()
ingredients = models.ManyToManyField(
Ingredient, related_name='recipes'
)
custom_field = JSONFieldSubclass(....)
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = ???
I know I could write a separate field and resolver pair for a Query, but I'd prefer it to be available as part of the schema for that model.
What I realize I could do:
class RecipeQuery:
custom_field = graphene.JSONString(id=graphene.ID(required=True))
def resolve_custom_field(self, info, **kwargs):
id = kwargs.get('id')
instance = get_item_by_id(id)
return instance.custom_field.to_json()
But -- this means a separate round trip, to get the id then get the custom_field for that item, right?
Is there a way I could have it seen as part of the RecipeType schema?
Ok, I can get it working by using:
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = graphene.JSONString(resolver=lambda my_obj, resolve_obj: my_obj.custom_field.to_json())
(the custom_field has a to_json method)
I figured it out without deeply figuring out what is happening in this map between graphene types and the django model field types.
It's based on this:
https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers
Same function name, but parameterized differently.

Django: How to limit_choices_to when using custom intermediate table

Let me start by saying that I am working with a legacy database so avoiding the custom intermediate table is not an option.
I'm looking for an alternative way to get the limit_choices_to functionality as I need to only present the options flagged by the sample_option boolean in the Sampletype Model in my ModelForm:
class PlanetForm(ModelForm):
class Meta:
model = Planet
fields = ['name', 'samples']
Here is a simplified view of my models
class Planet(models.Model):
name= models.CharField(unique=True, max_length=256)
samples = models.ManyToManyField('Sampletype', through='Sample')
class Sample(models.Model):
planet = models.ForeignKey(Planet, models.DO_NOTHING)
sampletype = models.ForeignKey('Sampletype', models.DO_NOTHING)
class Sampletype(models.Model):
name = models.CharField(unique=True, max_length=256)
sample_option = models.BooleanField(default=True)
Sample is the intermediate table.
Normally, if the project had been started with Django in the first place, I could just define the ManyToManyField declaration as:
samples = models.ManyToManyField('Sampletype', limit_choices_to={'sample_option'=True})
But this is not an option.. So how do I get this functionality ?
Django clearly states in their documentation that:
limit_choices_to has no effect when used on a ManyToManyField with a
custom intermediate table specified using the through parameter.
But they offer no information on how to get that limit in place when you DO have a custom intermediate table.
I tried setting the limit_choices_to option on the ForeignKey in the Sample Model like so:
sampletype = models.ForeignKey('Sampletype', models.DO_NOTHING, limit_choices_to={'sample_option': True})
but that had no effect.
Strangely, I find no answer to this on the web and clearly other people must have to do this in their projects so I'm guessing the solution is really simple but I cannot figure it out.
Thanks in advance for any help or suggestions.
You could set the choices in the __init__ method of the form:
class PlanetForm(ModelForm):
class Meta:
model = Planet
fields = ['name', 'samples']
def __init__(self, *args, **kwargs):
super(PlanetForm, self).__init__(*args, **kwargs)
sample_choices = list(
Sampletype.objects.filter(sample_option=True).values_list('id', 'name')
)
# set these choices on the 'samples' field.
self.fields['samples'].choices = sample_choices

Django: GenericForeignKey and unique_together

In the application I'm working on I'm trying to share access tokens within a company. Example: a local office can use the headquarter's tokens to post something on their Facebook page.
class AccessToken(models.Model):
"""Abstract class for Access tokens."""
owner = models.ForeignKey('publish.Publisher')
socialMediaChannel = models.IntegerField(
choices=socialMediaChannelList, null=False, blank=False
)
lastUpdate = models.DateField(auto_now=True)
class Meta:
abstract = True
Since Facebook, Twitter and other social media sites handle access tokens in their own way I made and abstract class AccessToken. Each site gets its own class e.g.
class FacebookAccessToken(AccessToken):
# class stuff
After doing some reading I found out that I must use a GenericForeignKey to point to classes that inherit AccessToken. I made the following class:
class ShareAccessToken(models.Model):
"""Share access tokens with other publishers."""
sharedWith = models.ForeignKey('publish.Publisher')
sharedBy = models.ForeignKey(User)
# for foreignkey to abstract model's children
contentType = models.ForeignKey(ContentType)
objectId = models.PositiveIntegerField()
contentObject = GenericForeignKey('contentType', 'objectId')
class Meta:
unique_together = (('contentObject', 'sharedWith'))
When I run the django test server I get the following error:
core.ShareAccessToken: (models.E016) 'unique_together' refers to field
'contentObject' which is not local to model 'ShareAccessToken'. HINT:
This issue may be caused by multi-table inheritance.
I don't understand why I get this error, first time using GenericForeignKey. What am I doing wrong?
If there is a smarter way to share the access tokens I would love to hear about it.
Your use of the generic foreign key in this situation is correct.
The error is coming from your unique_together declaration in your model. unique_together can only be used with columns that exist in the database. Since contentObject is not a real column, Django complains about the constraint.
Instead, you can do the following:
unique_together = (('contentType', 'contentId', 'sharedWidth'),)
This is equivalent to what you had defined in your question because contentObject is really just the combination of contentType and contentId behind the scenes.

Categories