Carousel Model Definition or Block - django

What is the difference in these two implementations of creating a carousel? They both seem to do the same thing, however one has Foreign Keys explicitly defined. The first implementation can easily be plugged in by calling it, meanwhile the second implementation has to be connected to a model via a ParentalKey. Essentially, which is the better option to to implement a carousel for display on a homepage?
class ImageCarouselBlock(blocks.StructBlock):
image = ImageChooserBlock()
caption = blocks.TextBlock(required=False)
page = PageChooserBlock()
class Meta:
icon = 'image'
class CarouselItem(LinkFields):
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
link_url = models.models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
caption = models.CharField(max_length=255, blank=True)
panels = [
ImageChooserPanel('image'),
FieldPanel('link_url'),
FieldPanel('caption'),
MultiFieldPanel(LinkFields.panels, "Link"),
]
class Meta:
abstract = True

The main benefit of the StructBlock / StreamField approach is the ability to mix different block types in the sequence - so, for example, you could define ImageCarouselBlock and VideoCarouselBlock to have a carousel that mixes images and videos.
If you only have one kind of object in the sequence, there's not much to choose between the two approaches. However, using a child model / InlinePanel is arguably nicer from a data modelling point of view, as it ensures that each object gets a real database entry (unlike StreamField where the data is stored in a single JSON field), meaning that you can run database queries against that data. (It's a bit hard to find a non-contrived example of why you'd want to do this with a carousel - but you could say things like "give me all of the NewsPages that include image X in their carousel".)

Related

How to get 2 different classes in 1 model field in django?

I am working in a project, where I have to merge/combine different classes into one field.
I want to do it that way, because I have two different classes ParkingArea1 and ParkingArea2 and I want to have the choice from the Admin and WebInterface, to select any of these two areas from a single drop-down list, and ideally store them in another's model field.
These are my models:
class ParkingArea1(models.Model):
zone_name = models.Charfield(max_length=255, unique=True)
# some more unique fields below
def __str__(self): return self.zone_name
class ParkingArea2(models.Model):
area_name = models.CharField(max_length=200, db_index=True, null=True, unique=True)
# some more unique fields below
def __str__(self): return self.area_name
class ChooseYourArea(models.Model):
area = # here I want a field, which should be able to hold any of the above classes
# some other stuff
How should I reform my models to achieve that?
NOTE: ParkingArea1 and ParkingArea2 are two actively used tables filled with data, so a change within these models wouldn't be optimal.
I researched a bit but couldn't find something to fit my case.
I understand that an abstract Base Class could be used for this case, but such a Base Class won't allow me to list all the areas that I want. Is there an obvious approach that I missed?
You can't if you want to use ForeignKeys. Both ParkingArea1 and ParkingArea2 have their own primary keys. Django won't know which table that foreign key belongs to. You need 2 fields.
There are ways around that but you need to ensure data consistency at the Django level, and the performance would be abysmal.
What I ended up doing, was to create another class ParkingAreas with two fields, where each field can hold either ParkingArea1 or ParkingArea2:
class ParkingAreas(models.Model):
area1 = models.OneToOneField(ParkingArea1, on_delete=models.SET_NULL, blank=True, null=True, default=None)
area2 = models.OneToOneField(ParkingArea2, on_delete=models.SET_NULL, blank=True, null=True, default=None)
def __str__(self):
if self.area1 is not None:
return self.area1.area1
else:
return self.area2.area2
class ChooseYourArea(models.Model):
area = models.ForeignKey(ParkingAreas, on_delete=models.SET_NULL, blank=True, null=True)
# some other stuff
Now I can get all parking areas in one place, even selectable with a drop-down menu, and store them in a ForeignKey field.
That way, when I need to access the areas, I can make some django queries and get what I need.

Dynamic views in Django ORM - best approach?

Some background
I am considering rebuilding an existing Laravel website with Django. It's a website that allows sharing benchmark data from drone/UAV propulsion components. Some benchmarks are done while testing multiple motors and propellers at the same time, which means a battery would be under the load of multiple motors, but it also means the airflow from one propeller has an impact on the data captured on the other propeller. This means the data is physically coupled. Here is an example. Right now I am trying to structure this project to allow upcoming features, and to see if Django ORM is a good fit.
Simplified Django models
class Benchmark(models.Model):
title = models.CharField()
private = models.BooleanField(default=False)
hide_torque = models.BooleanField(default=False)
class AbstractComponent(models.Model):
brand = models.CharField()
name = models.CharField()
class Meta:
abstract = True
class Motor(AbstractComponent):
shaft_diameter_mm = models.FloatField()
class Propeller(AbstractComponent):
diameter_in = models.FloatField()
class Battery(AbstractComponent):
capacity_kwh = models.FloatField()
class Powertrain(models.Model):
benchmark = models.ForeignKey(Benchmark, on_delete=models.CASCADE, related_name='powertrains')
motor = models.ForeignKey(Motor, on_delete=models.CASCADE)
propeller = models.ForeignKey(Propeller, on_delete=models.CASCADE, blank=True, null=True)
battery = models.ForeignKey(Battery, on_delete=models.CASCADE, blank=True, null=True)
class DerivedDataManager(models.Manager):
def get_queryset(self):
return super().get_queryset()\
.annotate(electrical_power_w=F('voltage_v') * F('current_a'))\
.annotate(mechanical_power_w=F('torque_nm') * F('rotation_speed_rad_per_s'))\
.annotate(motor_efficiency=F('mechanical_power_w') / F('electrical_power_w'))
class DataSample(models.Model):
powertrain = models.ForeignKey(Powertrain, on_delete=models.CASCADE, related_name='data')
time_s = models.FloatField()
voltage_v = models.FloatField(blank=True, null=True)
current_a = models.FloatField(blank=True, null=True)
rotation_speed_rad_per_s = models.FloatField(blank=True, null=True)
torque_nm = models.FloatField(blank=True, null=True)
thrust_n = models.FloatField(blank=True, null=True)
objects = models.Manager()
derived = DerivedDataManager()
class Meta:
constraints = [
models.UniqueConstraint(fields=['powertrain', 'time_s'], name='unique temporal sample')
]
Question
I was able to add "derived" measurements, like electrical_power_w to each row of the data, but I have no clue on how can I add derived measurements that combines the data of multiple drive trains within the same benchmark:
Assuming 3 powertrains, each with their own voltage and current data, how can I do:
Total_power = powertrain1.power + powertrain2.power + powertrain3.power
for each individual timestamp (time_s)? A total power is only meaningul if the Sum is made on simultaneously taken samples.
Goal
Without loading all the database data in Django, I would eventually want to get the 5 top benchmarks in terms of maximum total power, taking into account some business logic:
benchmarks marked as private are automatically excluded (until auth comes in)
benchmarks that opt to hide the torque automatically make the torque data, along as all the derived mechanical power and motor efficiency values go to None.
I would like to recreate this table, but with extra columns appended, like 'maximum thrust', etc... This table is paginated from within the database itself.
Hmmmm, this is quite a tricky one to navigate. I am going to start by adding the following annotation model method:
from django.db.models import Sum
I guess then it would be a case of adding:
.annotate(total_power=Sum(electrical_power_w))
But I think the issue is that each row in your DerivedDataManager queryset represents one DataSample which in turn links to one Powertrain via the ForeignKey field.
It would be better to do this in the business logic layer, grouping by the powertrain's UUID (you need to add this to your Powertrain model - see https://docs.djangoproject.com/en/3.0/ref/models/fields/#uuidfield for details of how to use this. Then, because you have grouped by, you can then apply the Sum annotation to the queryset.
So, I think you want to navigate down this path:
DataSample.objects.order_by(
'powertrain'
).aggregate(
total_price=Sum('electrical_power_w')
)

Best way to handle one ForeignKey field that can be sourced from multiple Database Tables

I am running into a little bit of unique problem and wanted to see which solution fit best practice or if I was missing anything in my design.
I have a model - it has a field on it that represents a metric. That metric is a foreign key to an object which can come from several database tables.
Idea one:
Multiple ForeignKey fields. I'll have the benefits of the cascade options, direct access to the foreign key model instance from MyModel, (although that's an easy property to add), and the related lookups. Pitfalls include needing to check an arbitrary number of fields on the model for a FK. Another is logic to make sure that only one FK field has a value at a given time (easy to check presave) although .update poses a problem. Then theres added space in the database from all of the columns, although that is less concerning.
class MyModel(models.Model):
source_one = models.ForeignKey(
SourceOne,
null=True,
blank=True,
on_delete=models.SET_NULL,
db_index=True
)
source_two = models.ForeignKey(
SourceTwo,
null=True,
blank=True,
on_delete=models.SET_NULL,
db_index=True
)
source_three = models.ForeignKey(
SourceThree,
null=True,
blank=True,
on_delete=models.SET_NULL,
db_index=True
)
Idea two:
Store a source_id and source on the model. Biggest concern I have with this is needing to maintain logic to set these fields to null if the source is deleted. It otherwise seems like a cleaner solution, but not sure if the overhead to make sure the data is accurate is worth it. I can probably write some logic in a delete hook on the fk models to clean MyModel up if necessary.
class MyModel(models.Model):
ONE = 1
TWO = 2
THREE = 3
SOURCES = (
(ONE, "SourceOne"),
(TWO, "SourceTwo"),
(THREE, "SourceThree")
)
source_id = models.PositiveIntegerField(null=True, blank=True)
source = models.PositiveIntegerField(null=True, blank=True, choices=SOURCES)
I would love the communities opinion.
Your second idea seems fragile as the integrity is not ensured by the database as you have pointed out yourself.
Without knowing more about the use case, it's difficult to provide an enlightened advice however if your "metric" object is refered by many other tables, I wonder if you should consider approaching this the other way round, i.e. defining the relationships from the models consuming this metric.
To exemplify, let's say that your project is a photo gallery and that your model represents a tag. Tags could be associated to photos, photo albums or users (e.g.. the tags they want to follow).
The approach would be as follow:
class Tag(models.Model):
pass
class Photo(models.Model):
tags = models.ManyToManyField(Tag)
class Album(models.Model):
tags = models.ManyToManyField(Tag)
class User(AbstractUser):
followed_tags = models.ManyToManyField(Tag)
You may even consider to factor in this relationship in an abstract model as outlined below:
class Tag(models.Model):
pass
class TaggedModel(models.Model):
tags = models.ManyToManyField(Tag)
class Meta:
abstract = True
class Photo(TaggedModel):
pass
As mentioned in the comments, you are looking for a Generic Relation:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class SourceA(models.Model):
name = models.CharField(max_length=45)
class SourceB(models.Model):
name = models.CharField(max_length=45)
class MyModel(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
source = GenericForeignKey('content_type', 'object_id')
There are three parts to setting up a Generic Relation:
Give your model a ForeignKey to ContentType. The usual name for this field is “content_type”.
Give your model a field that can store primary key values from the models you’ll be relating to. For most models, this means a PositiveIntegerField. The usual name for this field is “object_id”.
Give your model a GenericForeignKey, and pass it the names of the two fields described above. If these fields are named “content_type” and “object_id”, you can omit this – those are the default field names GenericForeignKey will look for.
Now you can pass any Source instance to the source field of MyModel, regardless of which model it belongs to:
source_a = SourceA.objects.first()
source_b = SourceB.objects.first()
MyModel.objects.create(source=source_a)
MyModel.objects.create(source=source_b)

How to make recursive django admin inline relationships?

I need to create an arbitrarily deep modular structure using Django admin. The nested_admin package has gotten me part of the way there, but there's a key piece of functionality that's still eluding me — the ability to create an object B inline off object A and then create another object A off that instance of object B. Here's a closer look at my models (simplified):
class ChatConditional(models.Model):
triggering_question = models.ForeignKey(
'texts.MessageChain',
on_delete=models.CASCADE,
)
keyword = models.CharField(max_length=50, blank=False, null=False)
keyword_response = models.ForeignKey(
'texts.MessageChain',
related_name='keyword_answer',
on_delete=models.CASCADE,
)
class MessageChain(models.Model):
conditional_trigger = models.ForeignKey(
'texts.ChatConditional',
blank=True, null=True,
)
message = models.TextField(max_length=160, blank=True, null=True)
So in the admin, I want to be able to create a ChatConditional to serve as a response to a certain MessageChain. Then once I create that Conditional, create another MessageChain to send that will potentially link to another Conditional. Here's what my admin models look like:
class SMSConditionalAdmin(nested_admin.NestedStackedInline):
model = ChatConditional
extra = 0
fk_name = "triggering_question"
inlines = [SMSChainAdmin]
class SMSChainAdmin(nested_admin.NestedStackedInline):
model = MessageChain
extra = 0
inlines = [SMSConditionalAdmin]
And now you can likely see the problem: In order to fully define the SMSConditionalAdmin, I need to set up an inline relationship to SMSChainAdmin, and vice versa. I might be missing something obvious, but how can I work around this need for the two admin inlines to recursively refer to one another?
Thanks in advance!

Optional additional data on ManyToManyField

I have a ManyToManyField in Django, and I want to save additional information for the relation. What I am doing is
class Speaker(models.Model):
name = models.CharField(max_length=50)
title = models.CharField(max_length=100, blank=True)
description = models.TextField(blank=True)
class Event(models.Model):
title = models.CharField(max_length=120)
speakers = models.ManyToManyField(Speaker, blank=True, null=True, through='Role')
class Role(models.Model):
speaker = models.ForeignKey(Speaker)
event = models.ForeignKey(Event)
role = models.CharField(max_length=50, blank=True)
As per documentation, this prevents Django from doing some automatic stuff. What is particularly annoying is that it makes the Speaker list not available when creating an Event in the admin.
I realize that in general Django does not know what to put in the Role.role field. But that is optional (blank=True). I would expect that
either Django recognizes that Role has only optional fields and lets me use the many to many relation as usual (creating the fields with an empty default), or
Django admin lets me add Speakers to a newly created event, and for each such Speaker it asks for the additional information (the value of Role.role).
The second possibility would be more useful and more general than the first. Still Django admin does none of the two: instead the speakers field is removed from the Event.
Is there a way to make Django admin behave as described above?
The solution lies in this answer. Briefly, one should use InlineModelAdmin, as documented here. This realizes exactly the second behaviour I described.