0

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.

zyeek
  • 1,277
  • 12
  • 27

2 Answers2

0

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).

Apollo Data
  • 1,267
  • 11
  • 20
  • I had considered this approach, but I would have to have to add validation on those fields, which may or not be an issue. Though it feels rather unnecessary. – zyeek May 24 '19 at 23:09
  • The validation would be performed by the form that saves the model depending on whether it is saving a user post or a group post. You could actually just have one form and, if it's a group post, add the group pk to the URL and then have the form view automatically add the value to the model. – Apollo Data May 24 '19 at 23:17
0

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

zyeek
  • 1,277
  • 12
  • 27