Here is a relationship I'm aiming for in terms of a User, Question, Bookmark relationship; Bookmark being an intermediary table:
A user can bookmark many Questions (topic pages)
A Question (topic page) can be bookmarked by several users
The keyword here being bookmark(ed), I have created a Bookmark model to show this relationship. However there's a problem of trying to make migrations due to a NameError being raised. Depending where they are defined in the script it's raising either:
NameError: name 'Question' is not defined
NameError: name 'Bookmark' is not defined
How can I get past this error in order to push the Bookmark into the migrations directory with its ForeignKey references?
class Question(models.Model):
title = models.CharField(unique=True, max_length=40)
body = models.TextField()
created = models.DateField(auto_now_add=True)
likes = models.IntegerField(default=0)
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True
)
views = models.ManyToManyField(
View,
related_name="+"
)
tags = models.ManyToManyField(
Tag,
related_name="questions"
)
bookmarks = models.ManyToManyField(
Bookmark,
related_name="+",
)
def __str__(self):
return self.title
class Bookmark(models.Model):
question = models.ForeignKey(
Question, on_delete=models.CASCADE,
related_name="+"
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
related_name="bookmarks"
)
Pass the related model as a string to the field constructor:
bookmarks = models.ManyToManyField(
'Bookmark',
related_name="+",
)
Django looks up the model class only when it's necessary, so it can support recursive foreign key shenanigans.
From the Django documentation:
If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself.
Related
I have 2 models Product and Resource. Resource has to be a TabularInline model in my admin panel. I am struggling with filtering resource titles that are related only to this product. Since it is a ForeignKey I should use select_related but I am not sure how to use it in my case. For now, the loop in my HTML file gives me all sales files (from all products).
models.py
class Product(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField('title', max_length=400, default='')
slug = models.SlugField(unique=True, blank=True, max_length=600)
class Resource(models.Model):
id = models.AutoField(primary_key=True)
type = models.CharField(max_length=32, choices=RESOURCE_TYPE, default='sales')
title = models.CharField(max_length=400, blank=False, null=False, default='')
related_files = models.FileField(upload_to='recources/', null=True, blank=True)
publish = models.DateTimeField('Published on', default=timezone.now)
resources = models.ForeignKey(Product, default='', on_delete=models.PROTECT, null=True, related_name='resources')
admin.py
class Resourceinline(admin.TabularInline):
model = Resource
class ProductAdmin(ImportExportModelAdmin):
inlines = [
Resourceinline,
]
resource_class = ProductResource
admin.site.register(Product, ProductAdmin)
views.py
class ProductDetailView(DetailView):
template_name = 'ProductDetailView.html'
model = Product
def get_context_data(self, **kwargs):
context = super(ProductDetailView, self).get_context_data(**kwargs)
resources_sales = Resource.objects.select_related('resources').filter(resources_id =1, type='sales') # not sure what to put here
context['resources_sales'] = resources_sales
return context
ProductDetailView.html
{% for resource in resources_sales.all %}
<p>{{resource.title}}</p>
{% endfor %}
Question
Where am I making the mistake and how can I display resource objects that are related to type=sales and are related only to this product in DetailView.
Edit
I realized that there is a column named resources_id that is connecting both models. Now I am struggling to filter it by id of current DetailView. I put resources_id=1 in my views.py but it must relate to DetailView that user is currently looking at. I tied to put resources_id=self.kwargs['id'] but it gives me KeyError at /product/test-product/ 'id' How can I do that?
since you are using generic DetailView you can refer to the current object with self.get_object(). actually that return the single object that view display. however you can use instateself.object too.
so you can filter the Product related Resources using Resource.objects.filter(resources=self.get_object(), type='sales')
you can read more Single object mixins
What I am trying to do is implement a 'follower system' for my app and I just want to make sure a user can't be followed multiple times by the same user. I can't seem to implement this as the admin continues to let me create duplicate relationships.
models.py
class FollowRelationshipManager(models.Manager):
def create_followrelationship(self, follower, followee, date):
if FollowRelationship.objects.filter(
follower=follower,
followee=followee
).exists():
raise ValueError('User is already followed')
else:
followrelationship = self.create(
follower=follower,
followee=followee,
date=date
)
return followrelationship
class FollowRelationship(models.Model):
follower = models.ForeignKey(User, related_name='follower', on_delete=models.CASCADE)
followee = models.ForeignKey(User, related_name='followee', on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True)
You can simply make use of a UniqueConstraint [Django-doc] which will prevent that the combination of the two (or more) fields:
class FollowRelationship(models.Model):
follower = models.ForeignKey(
User,
related_name='follower',
on_delete=models.CASCADE
)
followee = models.ForeignKey(
User,
related_name='followee',
on_delete=models.CASCADE
)
date = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['follower', 'followee'],
name='follow_once'
)
]
This will also be enforced at the database level.
Prior to django-2.2, you can use unique_together [Django-doc]:
# prior to Django-2.2
class FollowRelationship(models.Model):
follower = models.ForeignKey(
User,
related_name='follower',
on_delete=models.CASCADE
)
followee = models.ForeignKey(
User,
related_name='followee',
on_delete=models.CASCADE
)
date = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = [['follower', 'followee']]
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
I'm trying to perform a reversed query for a manytomany fields in Django, but it keeps gives me nothing, here is my code
models.py
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=120)
image = models.ImageField(upload_to='products')
branch = models.ManyToManyField(Branch, related_name='branches')
class Branch(models.Model):
area = models.ForeignKey(Area, on_delete=CASCADE)
name = models.CharField(max_length=1200)
phone = models.CharField(max_length=30, null=True, blank=True)
address = models.CharField(max_length=1200, null=True, blank=True)
tax_value = models.DecimalField(decimal_places=2, max_digits=4)
views.py
for branch in product_object.branches.all():
print(branch)
The branch is always nothing !!
For some reason, the related name is not calling it anymore. I called it using the model name (lower cased).
This is how it worked
for branch in product_object.branch.all():
Just to complete your answer above, I think the way you have your model set up is a little misleading and confusing. I think this would improve clarity:
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=120)
image = models.ImageField(upload_to='products')
branches = models.ManyToManyField(Branch, related_name='products')
Since you have a many to many field, a product can have multiple branches, the attribute name should reflect that
When you use the related_name, this would be if you are going from the m2m object. For example, if you have a branch, you could get all it's products by doing branch.products
UPDATE: I created a github repo with a full site demonstration of the problem.
Maybe my description below isn't quite communicating what I'm trying to do.
The github repo is: https://github.com/theCodeJerk/m2m-through
I really appreciate any help you may offer.
The code below is stripped down to illustrate the issue. While there are things that you may want to say "why would you do this anyway", there is probably a reason in the larger context :)
Here is my view:
class SubmissionCreate(CreateView):
model = Submission
fields = '__all__'
template_name_suffix = '_create_form'
success_url = '/'
Here is the relevant models.py code:
def custom_filename(instance, filename):
author = instance.publishers[0]
return 'papers/{0}.pdf'.format(author.pseudonum)
class Submission(models.Model):
name = models.CharField(
max_length=200,
blank=False
)
upload = models.FileField(
blank=True,
upload_to=custom_filename
)
publishers = models.ManyToManyField(
'Publisher',
blank=False,
related_name='publisher_of',
through='SubmissionPublisher'
)
class Publisher(models.Model):
user = models.ForeignKey(
User, blank=False,
on_delete=models.CASCADE
)
pseudonym = models.CharField(
max_length=200,
blank=False
)
class SubmissionPublisher(models.Model):
publisher = models.ForeignKey(
'Publisher',
blank=False,
on_delete=models.CASCADE
)
submission = models.ForeignKey(
'Submission',
blank=False,
on_delete=models.CASCADE
)
The problem is in the custom_filename, because I need the first publisher from the instance to generate the filename. The Submission is not yet saved when the SubmissionPublisher needs it to be saved.
What would the best way to do this be. Hopefully I have made sense here.
Thanks for any help!
Probably you can try like this:
First, update your custom_filename method:
def custom_filename(instance, filename):
if instance:
authors = instance.publishers.all()
if authors.exists():
author = authors[0]
return 'papers/{0}.pdf'.format(author.pseudonum)
return filename
Here I have fixed few issues, for example in your code instances.publishers[0] won't work, because you need to use a queryset method(like all(), or filter() etc) to access Publisher instances.
Then, make upload field nullable. Because you can't create M2M relations without creating Submission instance, and you can't create Submission instance with upload not null, because it requires an image.
class Submission(models.Model):
name = models.CharField(
max_length=200,
blank=False
)
upload = models.FileField(
null=True, default=None,
blank=True,
upload_to=custom_filename
)
Then, create a Form and override the save method:
from django import forms
from .models import Submission
class SubmissionForm(forms.ModelForm):
class Meta:
model = Submission
fields = '__all__'
def save(self, commit=True):
uploaded_file = self.cleaned_data.pop('upload')
instance = super().save(commit=True)
instance.upload = uploaded_file
instance.save()
return instance
Here I am pulling out the value for upload and saving the instance first. Then putting the image later. This code will work because upload field is nullable in your Submission model.
Finally, use that form class in your SubmissionCreate view:
class SubmissionCreate(CreateView):
model = Submission
form_class = SubmissionForm
template_name_suffix = '_create_form'
success_url = '/'
I am using Django sites framework (Django 2.1) to split an app into multiple sites. All of my models except the User model are site-specific. Here is my Post model:
post.py
class Post(models.Model):
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
related_name='children',
related_query_name='child',
blank=True,
null=True,
)
title = models.CharField(
max_length=255,
blank=True,
)
body_raw = models.TextField()
body_html = models.TextField(blank=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
I have no problem separating posts out by site. When I want to get the posts, I call:
posts = Post.on_site.filter(...)
I have a separate model called UserProfile. It is a many-to-one profile where there is a unique profile created for each user-site combination (similar to profile implementation at SE). The profile has a reputation attribute that I want to access when I get any post. This reputation attribute should be different for each site (like how on SE you have different rep on each site you are a member of).
user_profile.py
class UserProfile(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
reputation = models.PositiveIntegerField(default=1)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
on_site = CurrentSiteManager()
How do I access the user's username (on the User model) as well as the user's reputation (on the UserProfile model) when I get Posts from a query?
I'd like to do something like:
Post.on_site.filter(...)
.select_related('user__userprofile')
.filter_related(user__userprofile.site == get_current_site())
How do I filter a Many-To-One related model?
Better to make UserProfile -> User relationship to be OnetoOne,
because Django doesn't know which of many profiles to show
(but you also need to define related_name)
models.OneToOneField(get_user_model(), related_name='userprofile_rev')
Then you will be able to do this
qs = Post.on_site.filer().select_related('user', 'user__userprofile_rev')
for post in qs:
print(post.user.username, post.user.userprofile_rev.reputation)
If you don't want to change your DB structure you can do like this
(but you need to specify which profile to return)
qs = Post.on_site.filer().select_related('user').prefetch_related('user__userprofile_set')
for post in qs:
print(post.user.username, post.user.userprofile_set[0].reputation)