Make a nested reference in one Model class Table - django

I'd like to make a table with such a structure
1. urls
2. views
3. models
4. templates
4.1 html
4.2 format
4.3 test
html, format and test live in the same level with others but reference to 4. templates
I could start from the simple easy model as:
class Topic(models.Model):
"""A topic the user is learning about."""
title = models.CharField(max_length=200)
sub_title = models.CharField(max_length=200)
How to reference 'sub_title' to 'title'

This looks like a tree-like structure. A tree is basically a set of nodes (here topics) where a node can have at most one parent. There are no cycles in the parent relation (hence a node's grandparent can not be itself for example).
One can define such tree-like structure, by adding a reference to the parent, and that reference can be None (for top level nodes). So we can model this with:
class Topic(models.Model):
"""A topic the user is learning about."""
title = models.CharField(max_length=200)
parent = models.ForeignKey(
'Topic',
on_delete=models.SET_NULL,
null=True,
default=None,
)
Since your topics have an index number as well, this means that we can add an IntegerField with the index, and prefereably, the index and parent need to be unique together, such that we can not define two 4.2s:
class Topic(models.Model):
"""A topic the user is learning about."""
index = models.IntegerField()
title = models.CharField(max_length=200)
parent = models.ForeignKey(
'Topic',
on_delete=models.SET_NULL,
null=True,
default=None,
)
class Meta:
unique_together = (('parent', 'index'),)
ordering = ('index',)
So now we model the hierarchy with:
Topic.objects.create(index=1, title='urls')
Topic.objects.create(index=2, title='views')
Topic.objects.create(index=3, title='models')
templates = Topic.objects.create(index=4, title='templates')
Topic.objects.create(index=1, title='html', parent=templates)
Topic.objects.create(index=2, title='format', parent=templates)
tests = Topic.objects.create(index=3, title='tests', parent=templates)
Now we can obtain the parent of a topic, by calling .parent:
tests.parent # templates
We can also obtain an (ordered) set of subtopics with .topic_set:
templates.topic_set # queryset with [html, format, tests]

Related

how to filter data from parant model based on if there relationship with child model

I have these models
class Tree(models.Model):
field = models.TextField()
class TaskProgress(models.Model):
base_task = models.ForeignKey(BaseTask, on_delete=models.CASCADE)
tree = models.ForeignKey(Tree, on_delete=models.CASCADE)
class BaseTask(models.Model):
trees=models.ManyToManyField(Tree, through='TaskProgress')
class TaskType1(BaseTask):
child1_field = models.TextField()
class TaskType2(BaseTask):
child2_field = models.TextField()
how to get all taskprogress when related to TaskType2 ,
TaskProgress.objects.filter(???)
I added extra field on BaseTask class
TASK_TYPE =[('I','Irrigation'),('C','Care'),('A','Assessment'),('O','Other')]
class BaseTask(models.Model):
trees=models.ManyToManyField(Tree, through='TaskProgress')
worker = models.ManyToManyField(User)
task_type = models.CharField(max_length=1,choices=TASK_TYPE,null=True)
And the filter will be like this
TaskProgress.objects.filter(base_task__task = "I")
I do not think what you are asking is possible, if the models are designed like described. The base_task ForeignKey is specifically pointing at a BaseTask. Even though TaskType1 and TaskType2 inherit from BaseTask, they have no relation in the database. They only look similar.
Option 1: Look into Generic Relations in Django. Basically it allows you to have a ForeignKey relation with more than one type of model. I would not recommend it though. Generic relations are a mess if you don't know want you are doing.
Option 2: Rethink your layout. Maybe you can move the relation to the two TaskTypes instead and adress them via related_name.
class TaskProgress(models.Model):
# base_task = models.ForeignKey(BaseTask, on_delete=models.CASCADE)
tree = models.ForeignKey(Tree, on_delete=models.CASCADE)
class TaskType1(BaseTask):
task_progress = models.OneToOneField(TaskProgress, related_name='task_type_1'
child1_field = models.TextField()
class TaskType2(BaseTask):
task_progress = models.OneToOneField(TaskProgress, related_name='task_type_2'
child2_field = models.TextField()
This way you create a one-to-one-relation between the TaskProgress and the TaskType. You should be able to query one or the other by checking whether a relation exists, e.g. all TaskProgress instances with a relation to a TaskType1 instance.
# Query all TaskProgress instances, that have a TaskType1
TaskProgress.objects.filter(task_type_1__isnull=False)

Annotating values from filtered related objects -- Case, Subquery, or another method?

I have some models in Django:
# models.py, simplified here
class Category(models.Model):
"""The category an inventory item belongs to. Examples: car, truck, airplane"""
name = models.CharField(max_length=255)
class UserInterestCategory(models.Model):
"""
How interested is a user in a given category. `interest` can be set by any method, maybe a neural network or something like that
"""
user = models.ForeignKey(User, on_delete=models.CASCADE) # user is the stock Django user
category = models.ForeignKey(Category, on_delete=models.CASCADE)
interest = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0)])
class Item(models.Model):
"""This is a product that we have in stock, which we are trying to get a User to buy"""
model_number = models.CharField(max_length=40, default="New inventory item")
product_category = models.ForeignKey(Category, null=True, blank=True, on_delete=models.SET_NULL, verbose_name="Category")
I have a list view showing items, and I'm trying to sort by user_interest_category for the currently logged in user.
I have tried a couple different querysets and I'm not thrilled with them:
primary_queryset = Item.objects.all()
# this one works, and it's fast, but only finds items the users ALREADY has an interest in --
primary_queryset = primary_queryset.filter(product_category__userinterestcategory__user=self.request.user).annotate(
recommended = F('product_category__userinterestcategory__interest')
)
# this one works great but the baby jesus weeps at its slowness
# probably because we are iterating through every user, item, and userinterestcategory in the db
primary_queryset = primary_queryset.annotate(
recommended = Case(
When(product_category__userinterestcategory__user=self.request.user, then=F('product_category__userinterestcategory__interest')),
default=Value(0),
output_field=IntegerField(),
)
)
# this one works, but it's still a bit slow -- 2-3 seconds per query:
interest = Subquery(UserInterestCategory.objects.filter(category=OuterRef('product_category'), user=self.request.user).values('interest'))
primary_queryset = primary_queryset.annotate(interest)
The third method is workable, but it doesn't seem like the most efficient way to do things. Isn't there a better method than this?

Django: Annotate with field from another table (one-to-many)

Good day.
I wish to annotate my model with information from a different table.
class CompetitionTeam(models.Model):
competition_id = models.ForeignKey('Competition', on_delete=models.CASCADE, to_field='id', db_column='competition_id')
team_id = models.ForeignKey('Team', on_delete=models.CASCADE, to_field='id', null=True, db_column='team_id')
...
class Team(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
teamleader_id = models.ForeignKey('User', on_delete=models.CASCADE, to_field='id', db_column='teamleader_id')
...
class Competition(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=30)
...
Looping through my competitions, I wish to retrieve the list of competitionteam objects to be displayed with the relevant team's name. I tried:
CompetitionTeam.objects.filter(competition_id=_competition.id).filter(team_id__in=joined_team_ids).annotate(name=...)
-where instead of the ellipses I put Subquery expressions in. However, I'm unsure of how to match the team_id variable. eg.
*.anotate(name=Subquery(Team.objects.filter(id=competitionteam.team_id)).values('name'))
Related is the question: Django annotate field value from another model but I am unsure of how to implement that in this case. In that case, in place of mymodel_id, I used team_id but it only had parameters from the Team object, not my competition team object. I didn't really understand OuterRef but here is my attempt that failed:
CompetitionTeam.objects.filter(competition_id=_competition.id).filter(team_id__in=joined_team_ids).annotate(name=Subquery(Team.objects.get(id=OuterRef('team_id'))))
"Error: This queryset contains a reference to an outer query and may only be used in a subquery."
The solution for my question was:
CompetitionTeam.objects.filter(
competition_id=_competition.id,
team_id__in=joined_team_ids
).annotate(
name=Subquery(
Team.objects.filter(
id=OuterRef('team_id')
).values('name')
))
Thanks.

What is the simplest way to handle M2M through fields in wagtail FieldPanel?

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'),
]

Model with recursive self relation in Django's admin

Say we have a model with two self-recursive relations:
class Article(Item): # Item in this case is an abstract class
date = models.DateField()
parent = models.OneToOneField('self', null=True, blank=True)
subatricles = models.ForeignKey('self', null=True, blank=True, related_name='subs')
Article acts here as a node - it can has many children (if supplied) and one parent (if any). However, when I register my model in Django's admin my subatricles are shown as "one-to-one' - in both cases there are choice boxes but in the latter multiple values cannot be selected, though.
How can I add children via the admin pane to this Article object and later list them?
What I would like to have is this:
instead of normal drop-down.
Thanks.
You only need one field parent with subarticles as related_name to provide the reverse lookup:
class Article(Item): # Item in this case is an abstract class
date = models.DateField()
parent = models.ForeignKey('self', null=True, blank=True, related_name='subarticles')
so if you have an article object and you want to get its parent, use:
article.parent
if you want to get its children, you use:
article.subarticles
In the admin interface to show the subarticles the easiest way is to use the InlineModelAdmin:
class ArticleInline(admin.StackedInline):
model = Article
class ArticleAdmin(admin.ModelAdmin):
inlines = [
ArticleInline,
]