I have tried to implement constraints for UniqueConstraint for two foreign keys in the Django model. So far it has not been working as expected. Here below is the model definition :
class AssetMember(models.Model):
asset = models.ForeignKey(Asset, null=True, related_name='assetmember_asset', on_delete=models.CASCADE)
project = models.ForeignKey(Project, null=True, related_name='assetmember_project', on_delete=models.DO_NOTHING)
class Meta:
constraints = [
models.UniqueConstraint(fields=["asset", "project"], name="assetmember_unique_object")
]
Yet, when I try to create two assetmember objects with the same asset and project as foreign key, I can see that the constraints are not working as expected :
How shall I implement the model and the UniqueConstraint, so that it will not create the same object with asset and project twice?
Related
I've a model as shown below:
class Instance(models.Model):
data_version = models.IntegerField(default=1, unique=True)
name = models.CharField(max_length=200)
The data_version field has to be related to all other models in the application:
class Source(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
version = models.ForeignKey(Instance, on_delete=models.CASCADE, to_field='data_version', null=True)
The problem here is that Django requires the field data_version to be a unique field for me to be able to define such a relationship but that simply doesn't fit into my use case. I need to have multiple Instance objects in the app, each with version numbers starting from 1.
What I'd like is to have a unique constraint on the combination of name and data_version but then Django doesn't allow defining Foreign key relationships as shown above. Is there a way I can bypass this restriction?
I want to make Django Model fields unique with two fields(values) in some conditions.
there's two fields: 'team', 'type'. And I want to make team manager unique
For Example:
team=1, type='manager'
team=1, type='manager'
-> Not available
team=1, type='manager'
team=1, type='member'
team=1, type='member'
team=2, type='manager'
-> Available
I think unique_together('team', 'type') won't work properly with this situation.
How can I make this with Django Model?
Here's my model below:
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
I think, You need to use UniqueConstraint for your application which work perfect in kind of situation.
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
constraints = [
models.UniqueConstraint(fields=['team', 'type'], name='unique_team')
]
you can also refer this link for more understanding. and let me know if following solution will work.
Given that there is a deprecation warning in the documentation (based on 3.2 docs) for unique_together, I think it's worth showing that this can be done using UniqueConstraint. I believe that the key missing ingredient from the previous answer is the use of UniqueConstraint.condition, like so:
from django.db import models
from django.db.models import Q, UniqueConstraint
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
constraints = [
UniqueConstraint(
fields=['team', 'type'],
name='unique_team',
condition=Q(type='manager')
)
]
I recently added a "through" model to allow sorting connected objects.
In the example below, a Stage has an ordered list of Blocks linked through StageBlock (with the StageBlock.order field)
#register_snippet
class Block(index.Indexed, models.Model):
title = models.CharField(max_length=100, verbose_name=_("Block Name"))
#register_snippet
class Stage(index.Indexed, models.Model):
title = models.CharField(max_length=100, verbose_name=_("Stage Name"))
blocks = models.ManyToManyField(
to="app.Block",
blank=True,
help_text=_("Blocks associated to this stage"),
related_name="stages",
verbose_name=_("Blocks"),
through="StageBlock",
)
panels = [
FieldPanel("title", classname="title full"),
FieldPanel(
"blocks",
widget=autocomplete.ModelSelect2Multiple(
url="block-autocomplete",
attrs={"data-maximum-selection-length": 3},
),
),
class StageBlock(models.Model):
block = models.ForeignKey("app.Block", on_delete=models.CASCADE)
stage = models.ForeignKey("app.Stage", on_delete=models.CASCADE)
order = models.PositiveSmallIntegerField()
The problem is that the related Wagtail admin form breaks, since it tries to associate Block objects to Stage, without providing the "through" model "order" field value.
I'm wondering what is the cleanest/least effort solution to allow an ordered selection of elements in the admin panel, then to properly save the Stage instance with its blocks and related stageblocks.
For the moment, I will add a custom form to the snippet, and auto-assign the order from the position of blocks in the form data (hoping that it always matches the order of blocks as selected in the fieldpanel).
It feels like this use-case could be auto-handled, either by the wagtail-autocomplete plugin, or by wagtail fieldpanel.
But as far as I understand, fieldpanel will simply re-use the Django ModelMultipleChoiceField field, which returns a html element.
A many-to-many relation with a 'through' model is structurally the same as a one-to-many child relationship on that 'through' model, so one possibility is to implement this with an InlinePanel (as described here):
from django_modelcluster.fields import ParentalKey
from django_modelcluster.models import ClusterableModel
from wagtail.core.models import Orderable
#register_snippet
class Stage(index.Indexed, ClusterableModel):
title = models.CharField(max_length=100, verbose_name=_("Stage Name"))
panels = [
FieldPanel("title", classname="title full"),
InlinePanel("blocks", label="Blocks"),
]
class StageBlock(Orderable):
stage = ParentalKey("app.Stage", on_delete=models.CASCADE, related_name='blocks')
block = models.ForeignKey("app.Block", on_delete=models.CASCADE)
panels = [
FieldPanel('block'),
]
I have a Profile model and Photo model. Each profile has photos (One-to-many relationship) and a profile photo (One-to-one relationship). When I try to do this on django I get the error NameError: name 'Photo' is not defined
because I'm referencing the model before it's defined.
class Profile(models.Model):
name = models.CharField(max_length=50, primary_key=True, blank=False)
profile_photo = models.OneToOneField(
Photo,
null=True,
on_delete=models.SET_NULL
)
class Photo(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE)
photo = models.ImageField(primary_key=True, upload_to='photos/')
A possible solution could be using a boolean field on Photo to specify if it's a profile photo or not, but I think that's inefficient because I'd have to query all photos of the profile and make a loop to find the profile photo. Are there a better solution for that?
The problem is not the fact that you create two relations. Although it can cause some trouble (and therefore you better for example define related_names, etc.), the issue is that you refer to a class before it is constructed.
Indeed, Python reads files from top to bottom. In your file you define a class Profile with a reference to Photo, but at that point, there is no variable named Photo yet (whether that is a class or not is irrelevant, at least for Python).
You can solve this by using a string literal instead, Django will later, when all models are loaded, do the linking itself. So you can write:
class Profile(models.Model):
name = models.CharField(max_length=50, primary_key=True, blank=False)
profile_photo = models.OneToOneField(
'Photo',
null=True,
on_delete=models.SET_NULL
)
That being said, it is not entirely clear to me why you define two relations: Django automatically adds a reverse relation, so without specifying profile = ... in the Photo class, Django will automatically have added such profile relation. This will then work by using a JOIN (and querying the database in "reverse"). Unless the two relations are two different ways to combine the two models, it is better to use one relation, since this will avoid data duplication.
class Product( models.Model ):
name = models.CharField(verbose_name="Name", max_length=255, null=True, blank=True)
the_products_inside_combo = models.ManyToManyField('self', verbose_name="Products Inside Combo", help_text="Only for Combo Products", blank=True)
However, I got this error when I tried to put the duplicate values:
From_product-to_product relationship with this From product and To
product already exists.
Screencap of the error.
Each pair (Product, Product) must be unique. This is why you get already exists error.
Behind the scenes, Django creates an intermediary join table to
represent the many-to-many relationship.
What do you want to do is to have many-to-many relationship between two models (nevermind that they are the same) with additional information stored - quantity (so you would have ProductA = 2x ProductB + ....
In order to model this relationship you will have to create intermediary model and use through option. Documentation explains it very well, so have a look:
https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
Update
Here is minimal working example:
class Product(models.Model):
name = models.CharField(verbose_name='Name', max_length=255, null=True, blank=True)
products = models.ManyToManyField('self', through='ProductGroup', symmetrical=False)
def __str__(self):
return self.name
class ProductGroup(models.Model):
parent = models.ForeignKey('Product', related_name='+')
child = models.ForeignKey('Product', related_name='+')
and admin models:
class ProductGroupInline(admin.TabularInline):
model = Product.products.through
fk_name = 'parent'
class ProductAdmin(admin.ModelAdmin):
inlines = [
ProductGroupInline,
]
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductGroup)
As you can see recursive Product-Product relation is modeled with ProductGroup (through parameter). Couple of notes:
Many-to-many fields with intermediate tables must not be symmetrical, hence symmetrical=False. Details.
Reverse accessors for ProductGroup are disabled ('+') (in general you can just rename them, however, you don't want to work with ProductGroup directly). Otherwise we would get Reverse accessor for 'ProductGroup.child' clashes with reverse accessor for 'ProductGroup.parent'..
In order to have a nice display of ManyToMany in admin we have to use inline models (ProductGroupInline). Read about them in documentation. Please note, however, fk_name field. We have to specify this because ProductGroup itself is ambiguous - both fields are foreign keys to the same model.
Be cautious with recurrency. If you would define, for example, __str__ on Product as: return self.products having ProductGroup with the same parent as the child you would loop infinitely.
As you can see in the screencap pairs can be duplicated now. Alternatively you would just add quantity field to ProductGroup and check for duplication when creating objects.