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.
Related
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)
I am trying to change the default error message of django admin to my own message app that should display like a normal message system
here is my model for the app
class Role(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(null=True, unique=True, blank=False)
def __str__(self):
return self.name
in on other model this model become the forighn key. here is the second model
class LearningPath(models.Model):
Path_Name = models.CharField(max_length=500)
Role = models.ForeignKey(Role, on_delete=models.DO_NOTHING)
after that, I have created some roles and then some Learning Paths.
the issue is that I when I delete any of Role that is used inside any of Learning Path it shows me the below error
what I want is to show the error message in a normal message app as the normal alert div appear. I have tried to write some code in admin.py but it's not working.
here is admin.py code
class RoleAdmin(admin.ModelAdmin):
list_display = ('name',)
prepopulated_fields = {'slug': ('name',)}
list_per_page = 20
def delete_model(self, request, obj):
try:
obj.delete()
except IntegrityError:
messages.error(request, 'This object can not be deleted!')
admin.site.register(Role, RoleAdmin)
Please don't use on_delete=models.DO_NOTHING [Django-doc]. It relies on the fact that the database will somehow deal with it. But that behavior can be unpredictable. Some database can simply ignore this, other might prevent deleting the objects, or fail silently.
It might be better to use PROTECT [Django-doc]. This means that Django will identify the problem itself, and simply prevent querying the database in the first place to remove the object.
Django admin seems to take this into account as well. As we can read in the documentation of the get_deleted_objects(…) method:
(…)
This method must return a 4-tuple of (deleted_objects,
model_count, perms_needed, protected).
(…)
protected is a list of strings representing of all the protected related objects that can’t be deleted. The list is displayed in the template.
thanks to the Willem Van Onsem comment, my issue got fixed.
i have used on_delete=models.PROTECT into my model and it did work what i was looking for
In my django template <p>This user is unsubscribed {{sub.is_unsubscribed}}</p> always displays "This user is unsubcribed False" even when it should show True based on the following models.py
from django.shortcuts import get_object_or_404
class Subscriber(models.Model):
email = models.CharField(max_length=12)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
create_date = models.DateTimeField(auto_now_add=True)
def is_unsubscribed(self):
try:
get_object_or_404(MasterUnsubscriber, unsubcriber_email=self.email)
return True
except:
return False
def __str__(self):
return self.email
class MasterUnsubscriber(models.Model):
unsubscriber_email= models.CharField(max_length=12)
And for structural reasons of my app, I do not want to move the unsubscribe to the Subscriber model as a boolean. How can this be resolved while keeping same model formats.
get_object_or_404 will raise an error if there is no element at all, or when there are multiple such elements, since the underlying implementation is to use .get(..). You can make use of .exists() instead:
class Subscriber(models.Model):
# …
def is_unsubscribed(self):
return MasterUnsubscriber.objects.filter(unsubscriber_email=self.mail).exists()
That being said, I would advice to make use a ForeignKey [Django-doc] to the subscriber, not by matching the email address. This will be inefficient, since search on a non-indexed column is slow, and furthermore if the user later changes their email, then the all of a sudden, these can be subscribed again. Django also has convenient ways to filter or make aggregates on related objects, so you get more out of the Django ORM.
I have a django project and I have a Post model witch look like that:
class BasicPost(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
published = models.BooleanField(default=False)
created_date = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=False)
body = models.TextField(max_length=999)
media = models.ImageField(blank=True)
def get_absolute_url(self):
return reverse('basic_post', args=[str(self.pk)])
def __str__(self):
return self.title
Also, I use the basic User model that comes with the basic django app.
I want to save witch posts each user has read so I can send him posts he haven't read.
My question is what is the best way to do so, If I use Many to Many field, should I put it on the User model and save all the posts he read or should I do it in the other direction, put the Many to Many field in the Post model and save for each post witch user read it?
it's going to be more that 1 million + posts in the Post model and about 50,000 users and I want to do the best filters to return unread posts to the user
If I should use the first option, how do I expand the User model?
thanks!
On your first question (which way to go): I believe that ManyToMany by default creates indices in the DB for both foreign keys. Therefore, wherever you put the relation, in User or in BasicPost, you'll have the direct and reverse relationships working through an index. Django will create for you a pivot table with three columns like: (id, user_id, basic_post_id). Every access to this table will index through user_id or basic_post_id and check that there's a unique couple (user_id, basic_post_id), if any. So it's more within your application that you'll decide whether you filter from a 1 million set or from a 50k posts.
On your second question (how to overload User), it's generally recommended to subclass User from the very beginning. If that's too late and your project is too far advanced for that, you can do this in your models.py:
class BasicPost(models.Model):
# your code
readers = models.ManyToManyField(to='User', related_name="posts_already_read")
# "manually" add method to User class
def _unread_posts(user):
return BasicPost.objects.exclude(readers__in=user)
User.unread_posts = _unread_posts
Haven't run this code though! Hope this helps.
Could you have a separate ReadPost model instead of a potentially large m2m, which you could save when a user reads a post? That way you can just query the ReadPost models to get the data, instead of storing it all in the blog post.
Maybe something like this:
from django.utils import timezone
class UserReadPost(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.CASCADE, related_name="read_posts")
seen_at = models.DateTimeField(default=timezone.now)
post = models.ForeignKey(BasicPost, on_delete=models.CASCADE, related_name="read_by_users")
You could add a unique_together constraint to make sure that only one UserReadPost object is created for each user and post (to make sure you don't count any twice), and use get_or_create() when creating new records.
Then finding the posts a user has read is:
posts = UserReadPost.objects.filter(user=current_user).values_list("post", flat=True)
This could also be extended relatively easily. For example, if your BasicPost objects can be edited, you could add an updated_at field to the post. Then you could compare the seen_at of the UserReadPost field to the updated_at field of the BasicPost to check if they've seen the updated version.
Downside is you'd be creating a lot of rows in the DB for this table.
If you place your posts in chronological order (by created_at, for example), your option could be to extend user model with latest_read_post_id field.
This case:
class BasicPost(models.Model):
# your code
def is_read_by(self, user):
return self.id < user.latest_read_post_id
I'm developing a small Django site and I'm using django.contrib.admin to handle content management. I'd like to capture the first name & last name of the author (an Admin user) of an Article on its initial save (and not update it if another user edits the Article).
ie.
class Article(models.Model)
title = models.CharField(max_length=50)
pub_date = models.DateTimeField('date published')
author = ForeignKey(???)
...
What do I need to write to grab this user's first name & last name fields when creating a new Article object? I'd default to their admin username if those fields are blank.
Have your model use the User object:
author = models.ForeignKey(User)
To prevent this field from being changeable on update, check out this other SO post:
Django admin: exclude field on change form only
To change the admin's Select field to use first/last name, you could try this snippet:
http://djangosnippets.org/snippets/1642/
To change the admin's view, assuming you are using the built-in templates, you could add a custom column as described on this post: How do I add a custom column with a hyperlink in the django admin interface?
class AuthorAdmin(admin.ModelAdmin):
list_display = ('author_name',)
def my_author_name(self, obj):
if obj.author.first_name and obj.author.last_name:
return '%s %s' % (obj.author.first_name, obj.author.last_name)
else:
return obj.author.username
my_author_name.allow_tags = True
my_author_name.short_description = 'Author'
I think you are looking for this:
author = models.ForeignKey(User)
It looks like the best way to handle a None or blank result from get_full_name is to just populate User.author with models.ForeignKey(User) and then — at the template level — use the following:
{{ user.get_full_name|default:user.username }}
... via this SO answer. This allows me to perform queries on a User's Articles, but still gracefully handles blank first_name & last_name fields if a User hasn't entered them yet, but will also update dynamically when they have).