3

The current problem is that my form shows the logged in user all Portfolios ever created. The form should only show portfolios that the logged-in user created.

Something like this:

associated_portfolios manytomany field = ...objects.filter(user=user_id)

I'm not sure if this should be implemented in the forms.py or views.py and if so how. I've been going through the django documentation and found 'formfield_for_manytomany' but not sure if this is only meant for admin.

Models.py

class Portfolio(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    name = models.CharField(max_length=20)
    description = models.CharField(max_length=250, blank=True, null=True)

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=50)
    body = RichTextUploadingField(blank=True, null=True)
    associated_portfolios = models.ManyToManyField(Portfolio, blank=True)
    created_on = models.DateField(auto_now_add=True, editable=False)

Views.py

class PostCreate(CreateView):
    model = Post
    form_class = PostCreateForm

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        self.fields['associated_portfolios'] = Portfolio.objects.filter(user=self.request.user)
        return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)

forms.py

class PortfolioCreateForm(ModelForm):
    class Meta:
        model = Portfolio
        fields = ['user', 'name', 'description']    

class PostCreateForm(ModelForm):
    class Meta:
        model = Post
        fields = ['user', 'title', 'body', 'category', 'associated_portfolios']
Alex Winkler
  • 469
  • 4
  • 25

2 Answers2

2

Since you're using a ModelForm, the associated_protfolios field will be a ModelMultipleChoiceField [docs]. This field has a queryset attribute [docs]. We want to modify that attribute.

Django's CreateView has a method get_form, which in this case will grab your PostCreateForm. This is a good spot to filter the field's queryset, since we have access to the user:

class PostCreate(CreateView):
    model = Post
    form_class = PostCreateForm

    def get_form(self, *args, **kwargs):
        form = super().get_form(*args, **kwargs)  # Get the form as usual
        user = self.request.user
        form.fileds['associated_portfolios'].queryset = Portfolio.objects.filter(user=user)
        return form
Greg Kaleka
  • 1,942
  • 1
  • 15
  • 32
  • Really interesting approach. Thanks for the help. Currently getting AttributeError: 'PostCreateForm' object has no attribute 'fileds' - would it be right to try to add the queryset inside of the forms? associated_portfolios = forms.ModelChoiceField(queryset=... – Alex Winkler Feb 20 '20 at 11:09
  • Ahh! Just realized 'fileds' was a typo! Talk about tunnel vision. Your solution worked! Thanks a lot! Quick question why use form = super().get_form(*args, **kwargs) and not form = PostCreateForm() – Alex Winkler Feb 20 '20 at 11:17
  • 1
    Glad it worked! In this case, there's no difference (you can look at the docs for CreateView's get_form method and see it just returns an instance of `form_class` if one is given), except that calling `super().get_form()` is more DRY. If you were to change your form class name, you would only need to change it in one spot, rather than two if you used `form = PostCreateForm()`. Also, it's a best practice in cases like this. Sometimes there are other implementation details you might miss if you don't call super. – Greg Kaleka Feb 20 '20 at 16:12
0

Did you try this

self.fields['associated_portfolios'] = Post.objects.filter(associated_portfolios__portfolio__user=request.user)

OR

user_posts = Post.objects.filter(user=request.user)
self.fields['associated_portfolios'] = user_posts.associated_portfolios.all()

read more about M2M relationships querying here, because I think your problem may be with it.

Also, I'm not sure about your actual data maybe it's right and it gives a correct result as filtering Portfolio model against current user to get its objects looks right for me, but anyway double check everything again.

And as a final note, add related_name to your model fields so you can use it easily for reverse relations rather than going with Django's default naming, it will be clearer and give a better understanding.

Mahmoud Adel
  • 1,262
  • 2
  • 13
  • 23
  • Thanks for the attempt at a solution! Unfortunately, no luck with those solutions and ya I've read up and down that M2M Django doc but there's no examples on how to filter the fields. – Alex Winkler Feb 19 '20 at 09:37