0

I have two models Post and UserBookmarks.

models.py of Project/App1

from App2 import UserBookmarks

class Post(models.Model):
        id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
        author = models.ForeignKey(User, on_delete=models.CASCADE)
        title = models.CharField(verbose_name="Title", max_length=20)
        content = models.TextField(verbose_name="Post Content", max_length=2000)
        ...
        bookmarks = GenericRelation(UserBookmarks, related_query_name='post')

models.py of Project/App2

bookmarkable_models = models.Q(app_label='App', model='post') | models.Q(app_label='App', model='model-b') | models.Q(app_label='App', model='model-c')

class UserBookmarks(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)
    content_type = models.ForeignKey(ContentType, limit_choices_to=bookmarkable_models, on_delete=models.CASCADE, null=True, blank=False)
    object_id = models.CharField(max_length=50, blank=False)
    content_object = GenericForeignKey('content_type', 'object_id')
    date_added = models.DateTimeField(auto_now_add=True, blank=False)

Now I want to check if the user has already added the post to his bookmarks as my views.py but I don't understand how my query for this has to look like. Currently im doing something like this:

def post_add_bookmark(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == 'GET':
        if post.bookmarks.filter(user=request.user).exists():
            messages.error(request,
                           'You already added this Post to your Bookmarks')
            return redirect('post_detail', pk=post.pk)
        else:
            post.bookmarks.create(user=request.user, object_id=post.pk)
            messages.success(request, 'Post has been added to your Bookmarks')
            return redirect('post_detail', pk=post.pk)
    else:
        messages.error(request, 'Something went wrong')
        return redirect('post_detail', pk=post.pk)

Can smb. explain to me how this query has to look like in order to check if its already existing or not. currently im getting the following error:

Cannot query "peter123": Must be "UserBookmarks" instance.

1 Answers1

0

You can check if the post is already bookmarked by the user with:

post.bookmarks.filter(user=request.user).exists()

It might however be a good idea to enforce this at the database level, such that it is simply impossible to store the same bookmark for the same user:

class UserBookmarks(models.Model):
    # …

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['user, content_type', 'object_id'], name='unique_bookmark')
        ]

or prior to :

class UserBookmarks(models.Model):
    # …

    class Meta:
        unique_together = [['user, content_type', 'object_id']]
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • I updated my questions views.py accordingly. With your solution it seems I can add the same post x times as bookmark. Seems that the whole if statement is not checked for some reason –  Apr 04 '20 at 09:42
  • Even with your updated answere im running into an issue: App2.UserBookmarks: (models.E012) 'unique_together' refers to the nonexistent field 'user, content_type'. So i changed your syntax to: unique_together = ('user', 'content_type', 'object_id') wich also again runs into the following execption:App2_userbookmarks` (errno: 150 "Foreign key constraint is incorrectly formed")') –  Apr 04 '20 at 09:50
  • unique_together = [['user', 'content_type', 'object_id']] get the migration to work but still has no effect –  Apr 04 '20 at 09:55
  • @MarkusBrede: well then likely you use another `user`, `object_id`, or `content_type`. The `unique_together` is after all enforced at the *database* side. It will raise an `INTEGRITY ERROR`, if you try to create a second record with the same `user`, `object_id`, and `content_type`. So it looks to me that your view works with a different user, post primary key, etc. – Willem Van Onsem Apr 04 '20 at 09:57
  • I dont get the point on this as user == request.user so i should only be possible that i'm accessing the requesting user. I will try to build my function from scratch. –  Apr 04 '20 at 10:02
  • @MarkusBrede: can you see in the database a `UserBookmarks` item with as `content_type_id` the content type of `Post`, with `user` the primary key of the `peter123`, and with `object_id`, the primary key of the `Post` object you aim to bookmark? – Willem Van Onsem Apr 04 '20 at 10:07
  • I just check the database and there is a new object after I trigger my post_add_bookmark function and for each time I trigger it, a new bookmark object gets created. So I can indeed create unlimited bookmark objects for the same thing. Im on django 3.0.4 if that matters –  Apr 04 '20 at 10:17
  • Okay i Just found the mistake. So damn stupid. As django internaly query for object_id to establish the actuall reverse query and my object_id field is in practice encrypted, django was not able to accomplish the reverse lookup as it was always returning a different cryptographic key for each entry, fail. I first tought that I ran totaly transparent here with my encryption but it seems that my lib in background does not accomplish this for reverse lookups. Thank you very much for your help, again! –  Apr 04 '20 at 10:30
  • please update to answere to the correct syntax: unique_together = [['user', 'content_type', 'object_id']]. You are missing some quotes here –  Apr 04 '20 at 10:31