Many to Many and Foreign Key relations in django admin - python

First my django knowledge is beginner level, so please be patient with me.
I am faced with a model relationship that I do not know how to handle. I have 3 models: Project, Location and SubLocation.
A project can have multiple locations and each location can have many sublocations. I have a many to many field for location in the Project model and a foreignkey field for location in the Sublocation model.
class Project(models.Model):
...
locations = models.ManyToManyField(Location)
class Location(models.Model):
name = models.CharField(max_length=250, unique=True)
class SubLocation(models.Model):
location = models.ForeignKey(Location)
name = models.CharField(max_length=100, unique=True)
In django admin, I am able to add multiple locations when creating a project(Using filter_horizontal). However, I also need the option to select sublocations based on an added location for the project being created. I did not know how to do it with the above approach.
I then removed the locations many to many field from the project model tried the approach below.
I created a ProjectLocation model and added it as an inline to the Project ModelAdmin to be able to add locations and sublocations when creating a project. The model that looks as follows:
class ProjectLocation(models.Model):
project = models.ForeignKey(Project)
location = models.ManyToManyField(Location)
sublocations = models.ManyToManyField(SubLocation)
However, the approach does not work as desired since you can add any sublocations irregardless of the locations added. What I would like is to be able to add locations and their relevant sublocations when creating a project.
I read through generic relations as another possible approach but still I did not know how to pull it off.
With my model structure, is that possible?
If True, what should I do to get the desired result?
If False, please recommend how I could change it.

I think if you use foreign key it will be easier for your case, and it will easy to use with the _set option from django.

Yes it is possible but it will not be dynamic (meaning that changing the value of location will not magically update themselves without first saving the change) which might be very unpractical.
You should use formfield_for_manytomany in your admin -> https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_manytomany
The easiest way to implement this would be to add custom javascript filtering on that particular admin form

Leave for models as it was and try to use inlines in admin page.
So, your admins.py would look something like this:
class SubLocationInline(admin.StackedInline):
model = SubLocation
#admin.register(Location)
class LocationAdmin(admin.ModelAdmin):
.....
inlines = [SubLocationInline]

Related

Django ManyToMany with quantity

I'm having a bit of a problem trying to model something in Django that I've conceptualized. I know that it is a many to many relationship... however it is sort of self referential and has a quantity involved. I imagine this requires a bridge model of some sort, which I have, but now how do I edit them in the admin page?
What I have is a Component class. For example, a 'screw' is a component, and it requires no further components to create it. But so is a 'housing', which requires 4 screws to hold it together. That housing could then go into a manifold and the manifold could go into a vehicle and so forth.
Each thing could potentially be a component of another thing if that makes sense. I've put all of the screws and bolts and such into the database through the admin edit page. But now I want to start putting in more complex assemblies. I could just create an Assembly class which has a list of one or more components. But I'm still left with the problem that this assembly could go into a larger assembly zero or more times.
How do I represent that?
currently I have
class ComponentBase(models.Model)
name = models.CharField(max_length=255)
class Meta:
abstract = True
ordering = ['name']
class ItemComponent(ComponentBase):
components = models.ManyToManyField('ItemComponentWithQuantity', blank=True)
class ItemComponentWithQuantity(ItemComponent):
quantity = models.IntegerField(default=1)
assuming this is the correct way to model this (is it?) how do I get the admin edit form to set this up a bit like a spreadsheet or list?
like
name: manifold assembly
components:
10x screws
10x bolts
1x assembly housing
The components field should only show the ones that have been added and the quantity. Not all possible components.
i had my model originally set up to have components = models.ManyToManyField('ItemComponent', blank=True). This caused the admin panel to have a list of all the existing ItemComponents as expected, but obviously no quantities.
Adding the ItemComponentWithQuantity class in, I changed the manytomanyfield to ItemComponentWithQuantity. But now the admin components field is empty.
I hope I'm making sense here. I'm not sure what I'm doing incorrectly.
thanks in advance.
EK
The inheritance that you're doing is making this too complicated. You can model this a different way. Change your ComponentBase to a basic model.Model (not abstract) named Assembly. Change ItemComponent to just Component. Lastly change your ItemComponentWithQuantity to AssemblyComponent.
The AssemblyComponent should have the fields
assembly - ForeignKey to Assembly
component - ForeignKey to Component
quantity - IntegerField
Make another model AssemblyAssembly with the fields
assembly_parent - ForeignKey to Assembly
assembly_child - ForeignKey to Assembly
quantity - IntegerField
You would then create either a Tabular or Stacked inline admin (depends on desired user experience) for both AssemblyComponent and AssemblyAssembly. Be sure to set fk_name in the AssemblyAssembly to point to the assembly_parent as the fk_name because you have two foreign key fields referencing the same model. You can use a raw_id_field for the assembly_child if there will be a lot of possible assemblies in the system. Use the inline admins for the Assembly admin.
For more about inline admins, see: https://docs.djangoproject.com/en/4.0/ref/contrib/admin/#inlinemodeladmin-objects
That will give you roughly the admin UI that you want with the exception that component and assembly links will be in separate inline admins.
For this you can just have a single model and make it a recursive model where it is in a relationship with itself
component = models.ForeignKey('self', on_delete=models.CASCADE)
i also advice you add an extra field for quantity instead of creating a whole model for that. Hope this works.

django model change OneToOneField to ForeignKey with no DownTime

I made one field of my django model as OneToOneField. So, It's not possible to store duplicate FK value. I think changing OneToOneField to ForeignKey is the solution.
Current
class MyModel(models.Model):
...
abc = models.OneToOneField(YourModel, related_name='my_model', blank=True, null=True, on_delete=models.CASCADE)
...
Future
class MyModel(models.Model):
...
abc = models.ForeignKey(YourModel, related_name='my_model', blank=True, null=True, on_delete=models.CASCADE)
...
The problem is downtime when migrating. This model is an important model in my service, many requests come in even in a moment. It also has many data.
Is there a way to fix this without downtime?
And my service is using mysql 5.6 and django 2.2.
Option a)
Hmm so DB relation wise I don't see a difference, so what if you just adjust the field in the Model definition and modify the old migration that initially created the field? That way Django should think that there is nothing new to apply and treat the original OneToOne Field as a ForeignKey Field
Please try that on an a backup first to see if there are maybe additional unique constraints or so that you might have to remove in a custom sql command before you have a real ForeignKey Field.
Option b)
Use multiple migrations and deployments.
First add a new field (e.g. abc_new that is nullable)
Adjust your logic so always both fields are filled for new data and changes
Deploy this in a new release
Copy the "old" data from abc to abc_new
At this point you have two rows that contain the exact same data
Create a new release that drops the old abc column and renames abc_new to abc and remove the logic that contained this "sync" logic for the abc_new field

Django repeatable field with only new items

I want to create an Instruction that has multiple Steps. A simplified model would look like this:
class Step(models.Model):
description = models.CharField(max_length=255)
class Instruction(models.Model):
steps = models.ForeignKey(Step)
The problem is that I want to create a new Instruction with multiple steps, but when I create one in the admin, I should have a repeatable form field. For each step I can add a field and create a new Step. I do not need to be able to select an already existing step. I'm not sure if there is something OOTB of a package that already does that... Any ideas how to tackle this?
To give an example of what I'm trying to accomplish: the ACF repeater field in WP:
In my case I would only need a description field with the description of the step
You have things a bit backwards. The ForeignKey relationship should be the other way around (since an instruction can have many steps, but each step only has one related instruction...a Many-to-One relationship).
class Step(models.Model):
description = models.CharField(max_length=255)
instruction = models.ForeignKey(Instruction, related_name='steps')
class Instruction(models.Model):
# some fields
Now, in your Admin, you can use inlines to display these fields in a "repeatable" manner, similar to ACF.

Django: Access many-to-many (reverse) in the template

Following up with the posting regarding reversed many-to-many look ups, I was wondering what the best practice for my project/picture problem is:
I want to register a number of projects and the users can upload (but not required) multiple project pictures.
Therefore I defined the following two classes:
from easy_thumbnails.fields import ThumbnailerImageField
class Project(models.Model):
name = models.CharField(_('Title'), max_length=100,)
user = models.ForeignKey(User, verbose_name=_('user'),)
...
class ProjectPicture(models.Model):
project = models.ForeignKey('Project')
picture = ThumbnailerImageField(_('Image'),
upload_to='user/project_pictures/', null=True, blank=True,)
def __unicode__(self):
return u'%s\'s pictures' % (self.project.name)
So for every user, I am displaying their projects in a "dashboard" via
projects = Project.objects.filter(user = logged_user)
which returns a list of projects with the names, etc.
Now I would like to display a picture of the project in the dashboard table. Therefore I have two questions I am seeking advice for:
1) Is the class setup actually the best way to do it? I split up the classes like shown above to allow the users to upload more than one picture per project. Would there be a better way of doing it?
2) How can I display the first picture of a project in the template, if a picture is available? Do I need to make a query on every ProjectPicture object which corresponds to a Project? Or is there an elegant Django solution for that problem?
It's not many-to-many relation, you use foreign keys. It's normal setup. To access first picture in template you can use {{ project.projectpicture_set.all.0 }}, it will generate additional query. To avoid it use prefetch_related.

python django one-to-one relations between two models

I have two models and want to set a relation to them.
class ModelA(models.Model):
id = models.IntegerField(primary_key=True) # DB => PK, AI, NN
name = models.CharField(max_length=50)
...
class ModelB(models.Model):
modelA = models.OneToOneField("ModelA", primary_key=True)
description = models.CharField(max_length=255)
...
So I have a relationship between the two models. Is it possible to add a member to ModelA which stores the relation to ModelB without saving this relation to the database?
I would call it a dynamically created relation or something. Any hints oder suggestions how to let both models know each other?
I think it would be benefiting if the relation on one model can be done dynamically. Otherwise I'll get some trouble storing the models because one of the IDs won't be stored if I save one of the models.
I want to have the relation on both models so I can easily use the models as inline in django-admin.
regards
The reverse relation in Django is created by default.
To get the ModelA you will use:
ModelA.objects.filter(modelb__pk = 1)
You will find more details here:
https://docs.djangoproject.com/en/dev/topics/db/queries/
Django ORM will save ModelA first, then ModelB, in order to maintain data integrity in the DB.
Django can try saving multiple items in one transaction, and this way, if you cancel it, nothing will be saved, but this is possible in shell or in Python code. Over HTTP you can't maintain a transaction over several queries so far.
If you need to show model A as inline of model B, you need a custom admin interface, not new fields/models. I can't tell how to write custom admin widgets. I did do a similar thing with custom editor views & templates & Javascript. I stored the unsaved models in request.session.

Categories

Resources