I have a standard Django blog with a Post model, only on the model I have added a ManyToManyField for approvers, the idea being that the backend passes the post to 2 or more approvers to confirm the content before it is published.
class Post(models.Model):
author = models.ForeignKey(
get_user_model(), null=True, on_delete=models.SET_NULL)
title = models.CharField(max_length=30)
content = models.CharField(max_length=30)
approvers = models.ManyToManyField(Approvers)
I will probably learn towards something like django-fsm to create a finite state machine for the Post model to govern whether it is draft/in approval/published, but I would like to be able to change the approvers field so that the number and order of approvers (users) can be changed dynamically by the user.
What is the best way to do this? I thought I could try and change the approvers field to a JSONField so that users can add / delete / change the order of approvers and then handle the interpretation in the frontend and write some function to interface with django-fsm, but this feels like it conflates things too much. Am I missing a simpler route?
Why not make another model to do so like
class PostApprover(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='approvers')
user = models.ForeignKey(Approver, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
To access order in which post(let say with id 5) is approved (descending).you can do like
post = Post.objects.get(id=5)
post.approvers.order_by('-created_at')
you can change the value of created_at to change the order.
Or you can also make an integer field that determines your order
Related
Given the following model that stores the user's wish list for reading books:
class ReadingList(models.Model):
user_id = models.ForeignKey(UserInfo, on_delete=models.DO_NOTHING, null=False, blank=False, default=None, db_column='user_id')
book= models.CharField(max_length=255, blank=False)
creation_time = models.DateTimeField(blank=True)
class Meta:
unique_together = (('user_id', book),)
I want to create a model that helps in tracking the time spent in the reading the book on different days which looks something like this:
class ReadingTracker(models.Model):
user_id = models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='user', blank=False, db_column='user_id')
book= models.ForeignKey(ReadingList, on_delete=models.DO_NOTHING, related_name='book-to-read', blank=False, db_column='book')
time = models.DateTimeField(blank=True)
time_spent = models.floatfield()
On the client-side (corresponding to ReadingTracker) for both the fields user_id and book
I see that ReadingList object (1), ReadingList object (2), ... are listed. But, this is not working as expected.
What I want to achieve are the following:
For user_id field I want to see the something like dummy_uid1, dummy_uid2, ... to be listed.
Consider dummy_uid1 wants to read book1 and book2 whereas dummy_uid2 wants to read book1 and book3.
When dummy_uid1 is selected as user_id, I want only book1 and book2 to be listed for selection.
How do I define the model in django rest framework to achieve this?
Any suggestions related to the above would be much appreciated and thank you in advance.
There are two parts to this question:
If you want to see a different value than ReadingList object (1) then you need to define the __str__ value of your model, you can do this like so:
class ReadingList(models.Model):
...
def __str__(self):
return f'{self.user_id}' # return whatever string you want to display
If you want to just display the books for a particular user then you can use a filter() (see the Django documentation):
reading_list = ReadingList.objects.get(...)
ReadingTracker.objects.filter(user_id=reading_list)
However, I would add that you have a user_id on your ReadingList object which does seem to connect to a User model, but your user_id on ReadingTracker is a ForeignKey relation to ReadingList, which is confusing. I would suggest renaming the field or actually making it link to the User model (though this is unnecessary as you can still filter by User through the ReadingList model).
I am having trouble deciding how to structure my models for a particular data structure.
The models I have would be Posts, Groups, Users.
I want the Post model that can be posted from a groups page or user page and potentially more, like an events page.
Posts would contain fields for text, images(fk), user, view count, rating score (from -- a reference to where ever it was posted from like user or group page, though I am unsure how to make this connection yet)
I thought about using a Generic Foreign Key to assign a field to different models but read articles suggesting to avoid it. I tried the suggested models, but I wasn't unsure if they were the right approach for what I required.
At the moment I went with Alternative 4 - multi-table inheritance
class Group(models.Model):
name = models.CharField(max_length=64)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='_groups')
members = models.ManyToManyField(
settings.AUTH_USER_MODEL)
def __str__(self):
return f'{self.name} -- {self.created_by}'
def save(self, *args, **kwargs):
# https://stackoverflow.com/a/35647389/1294405
created = self._state.adding
super(Group, self).save(*args, **kwargs)
if created:
if not self.members.filter(pk=self.created_by.pk).exists():
self.members.add(self.created_by)
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_posts",
related_query_name="%(app_label)s_%(class)ss")
# class Meta:
# abstract = True
def __str__(self):
return f'{self.content} -- {self.created_by}'
class PostImage(models.Model):
image = models.ImageField(upload_to=unique_upload)
post = models.ForeignKey(
Post, related_name='images', on_delete=models.CASCADE)
def __str__(self):
return '{}'.format(self.image.name)
class UserPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_uPost', on_delete=models.CASCADE)
class GroupPost(models.Model):
post = models.OneToOneField(
Post, null=True, blank=True, related_name='_gPost', on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
To do some specific filters ex:
Filter specific group post
Post.objects.filter(_gPost__group=group)
Filter specific user post
Post.objects.filter(created_by=user) # exclude groups with _gPost__isnull=False
Create post to user/group
p = Post.objects.create(...)
up = UserPost.objects.create(post=p)
gp = GroupPost.objects.create(post=p)
Really I am wondering if this is a sensible approach. The current way of a filter and creating feel odd. So only thing making me hesitant on this approach is just how it looks.
So, is Generic ForeignKey the place to use here or the current multi-table approach. I tried going with inheritance with abstract = True and that was unable to work as I need a foreign key to base post model. Even with no abstract, I got the foreign key reference, but filter became frustrating.
Edit:
So far only weird issues(but not really) are when filtering I have to be explicit to exclude some field to get what I want, using only .filter(created_by=...) only would get all other intermediate tables.
Filter post excluding all other tablets would requirePost.objects.filter(_uPost__isnull=True, _gPost__isnull=True, _**__isnull=True) which could end up being tedious.
I think your approach is sensible and that's probably how I would structure it.
Another approach would be to move the Group and Event foreignkeys into the Post model and let them be NULL/None if the Post wasn't posted to a group or event. That improves performance a bit and makes the filters a bit more sensible, but I would avoid that approach if you think Posts can be added to many other models in the future (as you'd have to keep adding more and more foreignkeys).
At the moment I will stick with my current pattern.
Some extra reading for anyone interested.
https://www.slideshare.net/billkarwin/sql-antipatterns-strike-back/32-Polymorphic_Associations_Of_course_some
I'm currently working on a website (with Django), where people can write a story, which can be upvoted by themselves or by other people. Here are the classes for Profile, Story and Upvote:
class Profile(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=30, null=True)
last_name = models.CharField(max_length=30, null=True)
class Story(models.Model):
author = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="author")
title = models.CharField(max_length=50)
content = models.TextField(max_length=10000)
class Upvote(models.Model):
profile = models.ForeignKey('accounts.Profile', on_delete=models.CASCADE, related_name="upvoter")
story = models.ForeignKey('Story', on_delete=models.CASCADE, related_name="upvoted_story")
upvote_time = models.DateTimeField(auto_now=True)
As you can see, Upvote uses two foreign keys to store the upvoter and the related story. Now I want to make a query which gives me all the stories, sorted by the amount of upvotes they have. I've tried my best to come up with some queries myself, but it's not exactly what I'm searching for.
This one doesn't work at all, since it just gives me all the stories in the order they were created, for some reason. Also it contains duplicates, although I want them to be grouped by story.
hot_feed = Upvote.objects.annotate(upvote_count=Count('story')).order_by('-upvote_count')
This one kind of works. But if I'm trying to access a partical story in my template, it just gives me back the id. So I'm not able to fetch the title, author and content from that id, since it's just an integer, and not an object.
hot_feed = Upvote.objects.values('story').annotate(upvote_count=Count('story')).order_by('-upvote_count')
Could someone help me out with finding the query I'm searching for?
You are querying from the wrong model, you here basically fetch Upvotes ordered by the number of stories, or something similar.
But your probaby want to retrieve Storys by the number of upvotes, so you need to use Story as "queryset root", and annotate it with the number of upvotes:
Story.objects.annotate(
upvote_count=Count('upvoted_story')
).order_by('-upvote_count')
I think the related_name of your story is however a bit "misleading". The related_name is the name of the relation "in reverse", so probably a better name is upvotes:
class Upvote(models.Model):
profile = models.ForeignKey(
'accounts.Profile',
on_delete=models.CASCADE,
related_name='upvotes'
)
story = models.ForeignKey(
'Story',
on_delete=models.CASCADE,
related_name='upvotes'
)
upvote_time = models.DateTimeField(auto_now=True)
In that case the query is:
Story.objects.annotate(
upvote_count=Count('upvotes')
).order_by('-upvote_count')
I would like to create a view with a table that lists all changes (created/modified) that a user has made on/for any object.
The Django Admin site has similar functionality but this only works for objects created/altered in the admin.
All my models have, in addition to their specific fields, following general fields, that should be used for this purpose:
created_by = models.ForeignKey(User, verbose_name='Created by', related_name='%(class)s_created_items',)
modified_by = models.ForeignKey(User, verbose_name='Updated by', related_name='%(class)s_modified_items', null=True)
created = CreationDateTimeField(_('created'))
modified = ModificationDateTimeField(_('modified'))
I tried playing around with:
u = User.objects.get(pk=1)
u.myobject1_created_items.all()
u.myobject1_modified_items.all()
u.myobject2_created_items.all()
u.myobject2_modified_items.all()
... # repeat for >20 models
...and then grouping them together with itertool's chain(). But the result is not a QuerySet which makes it kind of non-Django and more difficult to handle.
I realize there are packages available that will do this for me, but is it possible to achieve what I want using the above models, without using external packages? The required fields (created_by/modified_by and their timefields) are in my database already anyway.
Any idea on the best way to handle this?
Django admin uses generic foreign keys to handle your case so you should probably do something like that. Let's take a look at how django admn does it (https://github.com/django/django/blob/master/django/contrib/admin/models.py):
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
So, you can add an additional model (LogEntry) that will hold a ForeignKey to the user that changed (added / modified) the object and a GenericForeignKey (https://docs.djangoproject.com/en/1.7/ref/contrib/contenttypes/#generic-relations) to the object that was modified.
Then, you can modify your views to add LogEntry objects when objects are modified. When you want to display all changes by a User, just do something like:
user = User.objects.get(pk=1)
changes = LogEntry.objects.filter(user=user)
# Now you can use changes for your requirement!
I've written a nice blog post about that (auditing objects in django) which could be useful: http://spapas.github.io/2015/01/21/django-model-auditing/#adding-simple-auditing-functionality-ourselves
I have a model where basically i am Tracking users' activities. I want to know what is the page the user have accessed MOST.
Here are my modals.
class Visitor(models.Model):
session_key = models.CharField(max_length=40, primary_key=True)
user = models.ForeignKey(User, related_name='visit_history', null=True, editable=False)
....
class Pageview(models.Model):
visitor = models.ForeignKey(Visitor, related_name='pageviews')
url = models.CharField(max_length=500)
method = models.CharField(max_length=20, null=True)
view_time = models.DateTimeField()
Here is my query.
Pageview.objects.values('visitor__user__first_name', 'visitor__user__last_name', 'visitor__user').annotate(url_count=Count('url')).annotate(url_count_unique=Count('url', distinct=True))
Here i am getting users number of urls visited, and number of unique urls visited.
Here i also want to know which is the url user have visited the most?
EDIT
Translation of my query.
Goto PageViews and count the occurring of unique URLS.(how many times a url have occurred.) and give me the one that have most visited count against each user.
I hope the question is clear, if not let me know.
IMHO you're better off with a many-to-many relationship. You would have something like:
class VisitedURLs(models.Model):
page = models.ForeignKey(Visitor, ....)
user = models.ForeignKey(User, ....)
timestamp = models.DateTimeField(auto_now_add=True)
and the original models become something like:
class Visitor(models.Model):
members = models.ManyToManyField(PageView, through='VisitedURLs')
class PageView(models.Model):
url = models.CharField(max_length=500)
method = models.CharField(max_length=20, null=True)
In this case, you can use the count/distinct on the visitedURLs model and when you get an object of that type you'll have a FK to a Visitor object (which would give you the user...) and a FK to the URL.
Another way is to explicitly count each unique visitor/url combination and store it somewhere. Depending on usage (e.g. if you want to compute/display this often) you may be better off with the dedicated storage.
Here is the solution that i have come up with.
Pageview.objects.filter(visitor__user_id=user['visitor__user_id']).values(
'url').annotate(page_count=Count('id')).order_by('-page_count')
if max_visited_node:
user['max_visited_node'] = max_visited_node[0]
by this way i can get the count of the all the pages the user have visited. then i order them by that count and then i get the top first element which contains the URL and page_count.
This is what i was looking for. the suggestion of Laur lvan is worth considerable.