Django Model queries with relationships. How to do the right join - django

Let's say I have 2 Models:
class Auction(models.Model):
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name="seller")
title = models.CharField(max_length=64)
class Watchlist(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_watchlist')
auction = models.ForeignKey(Auction, on_delete=models.CASCADE, related_name='auction_watchlist')
The view receives a request, creates a context variable with the auction objects the are:
associated with the user who made the request and
that have been added to the Watchlist Model,
sends it to the template.
I have set up my view to work like this:
#login_required
def watchlist(request):
watchlist_objects = Watchlist.objects.filter(user=request.user)
auction_objects = Auction.objects.filter(auction_watchlist__in=watchlist_objects).all()
context = {'watchlist_auctions': auction_objects}
print(context)
return render(request, "auctions/watchlist.html", context)
-I make the first query to get the list of items in the watchlist associate with the user.
-Then I use that to get another query from the Auction Model and I pass it to the template.
In the template I can access the attributes of Auction to display them. (title, author, and others that I did not include for simplicity)
The question is:
Is this the "right way? Is there a better way to access the attributes in Auction from the first Watchlist query?
It seems to me that I'm doing something overcomplicated.

This is not that bad, considering that it will probably be executed as one query, because of the lazy queryset evaluations. You can skip the .all() if you already have .filter().
However, there is a more convenient way to do this, using lookups that span relationships.:
auction_objects = Auction.objects.filter(auction_watchlist__user_id=request.user.id)

Related

Django inventory app tool cart update view

I'm making an inventory app to control the existence of tools in my workshop. Besides knowing how many things I have, I want to know where things are (what tool cart the tool is in ) and who owns the tool cart (Employee). I also need to keep a record of all damaged tools. I've been going about this in the following way:
1.- I have a model called Item that has all common filed for all tools, then I create a new model per tool type with specific field for each tool type i.e.(end-mill-cutters, drill-bits, screws, etc ). these tool Type models all inherit from Item as Multi-table inheritance.
2.- I made the models for my tools carts and its called Carritos( in spanish) this table has a One To One relation ship to Employees( since a carrito can be owned by one person only). It also has a Many To Many relationship to my Item table trough a secondary model called Transaccion, this model handles make the relation between Carrito and Items
this is the Carritos model
class Carritos(models.Model):
no_carrito = models.CharField(max_length=3, unique=True)
empleado = models.OneToOneField(Empleados, on_delete=models.CASCADE)
# empleado = models.ManyToManyField(Empleados, through='Transaccion')
items = models.ManyToManyField(Item, through='Transaccion', related_name='carritos')
f_creacion = models.DateTimeField(auto_now_add=True)
f_actualizacion = models.DateTimeField(auto_now=True)
activo = models.BooleanField(default=True)
def get_absolute_url(self):
return reverse('inventario:carrito')#, kwargs={'pk': self.pk})
class Meta:
verbose_name_plural = "Carritos"
def __str__(self):
return self.no_carrito
class Transaccion(models.Model):
carrito = models.ForeignKey(Carritos, on_delete=models.CASCADE, related_name='items_carrito')
herramienta = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='items_carrito')
cantidad = models.PositiveSmallIntegerField(default=1)
activo = models.BooleanField(default=True)
tipo = models.CharField(max_length=10, choices=CONSUMIBLE, blank=True, null=True)
motivo = models.CharField(max_length=10, blank=True, null=True)
def get_absolute_url(self):
return reverse('inventario:carrito')#, kwargs={'pk': self.pk})
3.- The idea I had to get the carritos logic is to get a list of existing carts in the carts main page and have a button bind to a CreateView CBV to create a new carrito if needed.
This list would also have a button bind to an UpdateView CBV in order to be able to change the employee in case the employee quits and an other button bind to a function that in theory would work as a DetailView to see all data assigned to carrito like (employee assigned to it, carrito number, and all Items in the carrito).
My intention was to be able to add an Item inside this view and have all items listed, I managed to be able to add Items and also managed to display all Items and the amount of those Items the carrito has. I had some issues on how to go about when multiple items of the same kind needed to be added to the carrito (let's say I needed to add 2 cutters exactly the same). But I figured that since I all ready had the Transaccion table, and this table tied Item to Carritos. I could use this to record every items as 1 of each and have an active field as Boolean, this way I could display and aggregate all distinct items and sum totals of every item in my view. It works for displaying the quantities.
The problem I'm currently are having, is if I want to edit a tool type and deactivate one of the items in the transaction model I always get the firs items on the list no matter how I choose to filter it.
My views for carritos creation
# =========================================================================== #
# LOGICA PARA CREAR CARRITOS
# =========================================================================== #
# ===================> Logica relacinado con Cortadores <=====================#
def home_carrito(request):
template_name = 'inventario/carrito/createcarrito.html'
model = Carritos
carritos = Carritos.objects.all()
if carritos:
return render(request, template_name, {'carritos':carritos})
else:
return render(request,template_name)
class CarritoCreate(CreateView):
model = Carritos
fields = [
'no_carrito',
'empleado',
'activo',
]
class ItemCreate(CreateView):
model = Transaccion
fields = [
'carrito',
'herramienta',
]
def detalle_carrito(request, pk):
model = Carritos, Transaccion
template_name = 'inventario/carrito/detalles_carrito.html'
carritos = Carritos.objects.filter(pk=pk)
# GEST ALL TOOLS ASSIGNE TO CARRITO'S PK THAT ARE ACTIVE
# TRY TO GET ALL ACTIVE ITEMS THAT BELONG TO CARRITO = PK AND AGREGATE TOTAL ITEMS PER TYPE
cantidades = Transaccion.objects.values('herramienta__description').annotate(Sum('cantidad')).filter(activo=True, carrito_id=pk)
# GEST ALL TOOLS ASSIGNE TO CARRITO'S PK THAT ARE NOT ACTIVE
eliminados = Transaccion.objects.filter(activo=False,carrito_id=pk)
return render(request,template_name, {'carrito':carritos, 'trans':cantidades, 'eliminados':eliminados})
class CarritoUpdate(UpdateView):
model = Carritos
fields = [
'no_carrito',
'empleado',
'activo',
]
template_name_suffix = '_update_form'
def ItemUpdate(UpdateView):
model = Transaccion
fields = [
'carrito',
'herramienta',
'cantidad',
'tipo',
'motivo',
'activo',
]
template_name_suffix = '_update_form'
def detalle_Items(request, pk):
model = Transaccion
template_name = 'inventario/carrito/test-template.html'
try:
items_update = Transaccion.objects.filter(activo=True, carrito_id=pk, herramienta_id=pk)
except Transaccion.DoesNotExist:
raise Http404()
return render(request, template_name, {'items_update':items_update})
So what I need in the first place is to know if what I'm doing is logical? or make sense. Scond thing I need is to know if there a better way and how?
and finally I need help resolving my issue: I need to get into an updateview for every Item in my Transaccion model and be able to disable or enable that record.
Different people would take different approaches. So far I think what you have built would absolutely work, but what becomes complicated is building the realtime inventories, which looks like its built from the transaction log. What I see as missing from the model is the snapshot of real time inventories available. Because we aren't talking about a data model that changes so frequently - like ad impressions - you can store that upon transaction vice computing it as needed.
For instance, your global inventory of hammers is 5 hammers. One employee adds a hammer to a cart. From here you articulated a couple of different use cases. One is that you need to know that employee XYZ (which implies a specific cart based on the 1-to-1) has that specific hammer. What you'd also like to know is how many hammers you have available? You may also want to understand the turnover of specific assets. Does employee XYZ maintain items in his cart longer than the average employee?
To do this I think you'd need to talk about the API layer which orchestrates that logic and the addition of another object which snapshots actual inventory instead of computing that from the transaction log. Why I bring up the API layer is that it may be a cleaner abstraction to place the logic for orchestrating multiple model changes in that than having the model itself house that logic.
So in short, I think what you've built works - but the logical expression of the use cases you've articulated are handled at the viewset/ modelviewset layer in an API. Because thats where you'll need to prep the data to be loaded into a specific format for visualization. And thats where what is easily serializable becomes the dominant force in model complexity.

Django change multiple model entries

I have a model containing various entries tied to one user and I want to give the user a view where he can review these entries, select some of them and perform an action on the selection. something like the admin intereface has. I have tried UpdateView but that is for one entry only. ListView doesn't like that the model returns multiple entries for one identificator. Is there something else I could use?
EDIT:
Below is the model, I am talking about. A user will have multiple model entries and I just want a view that lists these multiple entries and allows the user to perform a bulk action on them, like delete ...
class UserData(models.Model):
class Meta:
app_label = "app"
user_id = models.IntegerField()
name = models.CharField(_("Name"),max_length=100)
latdeg = models.IntegerField(_('Latitude'))
latmin= models.IntegerField(_('Latitude'), validators=[validate_60])
londeg = models.IntegerField(_('Longitude'))
lonmin= models.IntegerField(_('Longitude'), validators=[validate_60])
main = models.BooleanField()
def __unicode__(self):
return user_id + "-" + self.name
I think what you are looking for is inlineformset_factory
Since you have not given any example, I suggest you look at the example of One author, multiple books as given in this SO post.

Filter and count with django

Suppose I have a Post and Vote tables.
Each post can be either liked or disliked (this is the post_type).
class Post(models.Model):
author = models.ForeignKey(User)
title = models.CharField(verbose_name=_("title"), max_length=100, null=True, blank=True)
content = models.TextField(verbose_name=_("content"), unique=True)
ip = models.CharField(verbose_name=_("ip"), max_length=15)
class Vote(models.Model):
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
post_type = models.PositiveSmallIntegerField(_('post_type'))
I want to get posts and annotate each post with number of likes.
What is the best way to do this?
You should make a function in Post model and call this whenever you need the count.
class Post(models.Model):
...
def likes_count(self):
return self.vote_set.filter(post_type=1).count()
Use it like this:
p = Post.objects.get(pk=1)
print p.likes_count()
One approach is to add a method to the Post class that fetches this count, as shown by #sachin-gupta. However this will generate one extra query for every post that you fetch. If you are fetching posts and their counts in bulk, this is not desirable.
You could annotate the posts in bulk but I don't think your current model structure will allow it, because you cannot filter within an annotation. You could consider changing your structure as follows:
class Vote(models.Model):
"""
An abstract vote model.
"""
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
class Meta:
abstract = True
class LikeVote(Vote)
pass
class DislikeVote(Vote)
pass
i.e., instead of storing likes and dislikes in one model, you have a separate model for each. Now, you can annotate your posts in bulk, in a single query:
from django.db.models import Count
posts = Post.objects.all().annotate(Count('likevote_set'))
for post in posts:
print post.likevote__count
Of course, whether or not this is feasible depends on the architecture of the rest of your app, and how many "vote types" you are planning to have. However if you are going to be querying the vote counts of posts frequently then you will need to try and avoid a large number of database queries.

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

django views - accessing a m2m field in a generic view

I've stumbled upon this issue and my noob brain got fried trying to resolve it. I feel like there's some basic concepts here that I'm missing.
So I have this "Films" model with category choice field and a m2m relationship to a "Directors" model, and I'm trying to write 2 different views, one that returns a list of films filtered by category and one that returns a list of films filtered by director.
The first one is easy, but I just don't know how to get the director model's name field to create the second filter.
So I have this models (i've taken the irrelevant stuff out including the category thing i mentioned above)
class Director(models.Model):
name = models.CharField(max_length=50)
web = models.URLField(blank=True, help_text= "opcional")
class Film(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length= 15)
director = models.ManyToManyField(Director, blank=True, help_text= "opcional")
this url
(r'^peliculas/director/(?P<director>\w+)/$', 'filtered_by_director'),
and this view
def filtered_by_director(request,director):
return list_detail.object_list(
request,
queryset = Film.objects.filter(director.name=director),
template_name ='sections/film_list.html',
template_object_name = 'film',
paginate_by = 3
)
The same template is supposed to be used by both views to render the relevant list of objects
The view doesn't like the filter i'm using at the queryset for the m2m field, but I have no clue how to do it really, I've tried whatever I could think of and it gives me a "keyword can't be an expression" error
Any help to this lowly noob will be appreciated.
Line queryset = Film.objects.filter(director.name=director),
needs to read: queryset = Film.objects.filter(director__name=director),
Field lookups are done by __ double underscore syntax:
http://docs.djangoproject.com/en/dev/topics/db/queries/#field-lookups
In your filter, try specifying the director name like (documentation):
filter(director__name=director)