Django queryset how to get last n records? - django

I have an app where I want to display in template last 5 comments for every message. How can I get last n comments that matches a specific message? I've tried: comments_all = Comments.objects.all().order_by('-id')[:5] but it just returns 5 last comments regardless of message.
models
class Message(models.Model):
host = models.ForeignKey(NewUser, on_delete=models.CASCADE)
body = models.TextField(max_length=1000)
created = models.DateTimeField(auto_now_add=True)
class Comments(models.Model):
message = models.ForeignKey(Message, on_delete=models.CASCADE)
publisher = models.ForeignKey(NewUser, on_delete=models.CASCADE)
body = models.TextField(max_length=300)
created = models.DateTimeField(auto_now_add=True)
views
def home(request):
messages_all = Message.objects.all()
comments_all = Comments.objects.all()
form = AddComments()
if request.method == "POST":
form = AddComments(request.POST)
if form.is_valid():
comment = form.save(commit=False)
messageid = request.POST.get('message_id')
comment.message_id = messageid
comment.publisher = request.user
comment.save()
return redirect('home')
last_five = Message.objects.all().order_by('-id')[:10]
context = {
'messages_all':messages_all,
'comments_all':comments_all,
'form':form,
'last_five':last_five
}
return render(request,'base/home.html', context)

def home(request):
messages_all = Message.objects.all()
...
# construct a dictionary where each message.id is the key
# and the value is the queryset for last 5 comments for the
# matching message
message_last_five = {
msg.id: Comments.objects.filter(message=msg).order_by('-created')[:5]
for msg in messages_all
}
# add message_last_five to your context
context = {
'messages_all': messages_all,
'comments_all': comments_all,
'message_last_five': message_last_five,
'form': form,
'last_five': last_five
}
return render(request,'base/home.html', context)
Although this perhaps can get you what you need - know that this is not an optimal solution because this is quite an expensive query to run.
And you can also achieve relatively the same thing with django templating instead too.

Related

Save two model (One contains foreign key) data from a single template

I'm pretty new to django and working on a blog based project where user can add ratings to specific type of review post.For example giving stars are enabled for restaurant but not for tech stores.
I have created two different form for "review" and "star" model. I want to rate the restaurant using the model named "star" and do it in same template.But I'm having difficulties to do that.
Im getting this error.
"The above exception (NOT NULL constraint failed: reviews_star.post_id_id) was the direct cause of the following exception:"
How do I get the review id that I just save with "review_form.save()".
my review model kinda looks like this (Removed other attributes which aren't related to this problem):
class Review(models.Model):
review_title = models.CharField(verbose_name='Title', max_length=100)
review_body = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
restaurant_or_techstore = models.CharField(verbose_name='Foods or Travel',max_length=20)
slug = models.SlugField(null=False,unique=True,max_length = 300)
My rating model looks like this:
class Star(models.Model):
post_id = models.ForeignKey(Review, on_delete = models.CASCADE )
food = models.FloatField(verbose_name='Food',null=False)
service = models.FloatField(verbose_name='Food',null=False)
cleanliness = models.FloatField(verbose_name='Food',null=False)
and my view :
def CreateReview(request):
ImageFormSet = modelformset_factory(Image,form=ImageForm,extra=5)
if request.method == 'POST':
reviewForm = ReviewForm(request.POST)
formset = ImageFormSet(request.POST,request.FILES,queryset=Image.objects.none())
starsForm = StarsrForm(request.POST)
if reviewForm.is_valid() and formset.is_valid() and starsForm.is_valid():
review_form = reviewForm.save(commit=False)
review_form.author = request.user
review_form.post_or_discussion = 1
review_form.food_or_travel = 'Foods'
review_form.save()
reviewForm.save_m2m()
starsForm.save()
for form in formset.cleaned_data:
if form:
image = form['image']
photo = Image(review=review_form,image=image)
photo.save()
messages.success(request,'Image Uploaded Successfully')
return HttpResponseRedirect("/")
else:
print(reviewForm.errors, formset.errors)
else:
reviewForm = ReviewForm()
starsForm = StarsrForm()
formset = ImageFormSet(queryset=Image.objects.none())
return render(request,'reviews/review_form.html',{'reviewForm':reviewForm,'formset':formset,'starsForm':starsForm})
best try when you use ForeighKey is to use related_name attr for this field
class Star(models.Model):
post = models.ForeignKey(Review, related_name='post_review', on_delete = models.CASCADE )
after it you can refer to star from review by some_object_review.post_review.some_field_star
For you error above try reviews_star.post_id.id
Solve The problem.All I had to do is pass the review_form instance to the star form.
new view is:
if request.method == 'POST':
reviewForm = ReviewForm(request.POST)
formset = ImageFormSet(request.POST,request.FILES,queryset=Image.objects.none())
starsForm = StarsrForm(request.POST)
if reviewForm.is_valid() and formset.is_valid() and starsForm.is_valid():
review_form = reviewForm.save(commit=False)
review_form.author = request.user
review_form.post_or_discussion = 1
review_form.food_or_travel = 'Foods'
review_form.save()
reviewForm.save_m2m()
instance = review_form
print(instance.id)
star_form = starsForm.save(commit=False)
star_form.post_id = instance
star_form.save()
for form in formset.cleaned_data:
if form:
image = form['image']
photo = Image(review=review_form,image=image)
photo.save()
messages.success(request,'Image Uploaded Successfully')
return HttpResponseRedirect("/")
else:
print(reviewForm.errors, formset.errors)
else:
reviewForm = ReviewForm()
starsForm = StarsrForm()
formset = ImageFormSet(queryset=Image.objects.none())
return render(request,'reviews/review_form.html',{'reviewForm':reviewForm,'formset':formset,'starsForm':starsForm})

How to retrieve user selections from a previous form

I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')
To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)
At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})

Message notification in django

i have simple message model in my study project.
class Message(models.Model):
sender = models.ForeignKey(CustomUser, related_name='sender')
reciever = models.ForeignKey(CustomUser, related_name='reciever')
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
How can I send notification to request.user about new message.
i.e. I need send notification to request.user if I have new Message object with request.user in reciever field
UPD my view:
def dialog(request, user_pk):
sent = Message.objects.filter(reciever_id=user_pk, sender_id=request.user.pk)
recieved = Message.objects.filter(reciever_id=request.user.pk, sender_id=user_pk)
mate = CustomUser.objects.get(pk=user_pk)
dialog_list = sorted(chain(sent, recieved), key=lambda a:a.created_at)
if request.POST:
form = MessageForm(request.POST)
if form.is_valid():
f = form.save(commit=False)
f.sender = CustomUser.objects.get(pk=request.user.pk)
f.reciever = CustomUser.objects.get(pk=user_pk)
form.save()
else:
form=MessageForm()
return render(request, 'dialog.html', {'sent':sent,
'recieved':recieved, 'form':form, 'mate':mate, 'dialog_list':dialog_list})
look's like this:
I solved this problem by making some ugly logic
Update my model with boolean field:
class Message(models.Model):
sender = models.ForeignKey(CustomUser, related_name='sender')
reciever = models.ForeignKey(CustomUser, related_name='reciever')
text = models.TextField(verbose_name='')
created_at = models.DateTimeField(auto_now_add=True)
is_readed = models.BooleanField(default=False)
In view I create set for my message and add users here, who have messages with is_readed == False
It become True when we load dialog page with this user
I never working in real project, but I think this solution unacceptable here.
Anyway in my first study project it's worked, maybe it helped some newbies, like meemphasized text
def dialog(request, user_pk):
sent = Message.objects.filter(reciever_id=user_pk, sender_id=request.user.pk)
recieved = Message.objects.filter(reciever_id=request.user.pk, sender_id=user_pk)
not_readed = set()
for message in recieved:
message.is_readed = True
message.save()
for message in Message.objects.filter(reciever_id=request.user.pk):
if message.is_readed ==False:
not_readed.add(CustomUser.objects.get(pk=message.sender_id))
dialog_list = sorted(chain(sent, recieved), key=lambda a:a.created_at)
if request.POST:
form = MessageForm(request.POST)
if form.is_valid():
f = form.save(commit=False)
f.sender = CustomUser.objects.get(pk=request.user.pk)
f.reciever = CustomUser.objects.get(pk=user_pk)
form.save()
else:
form=MessageForm()
return render(request, 'dialog.html', {'sent':sent,
'recieved':recieved, 'form':form, 'mate':mate,
'dialog_list':dialog_list, 'not_readed':not_readed})

Django form validation and action on two models

I haven't got much experience with building custom forms (only default model forms) and I'm looking for some help here. I'm trying to build a form that will get "feed_url" from user and if it already exist, just add reference into UserFeed model. In case it doesn't exist, it should add it to the Feed model and also reference it in UserFeed model as well.
models
class Category(models.Model):
name = models.CharField(unique=False, max_length=64)
user = models.ForeignKey(User)
slug = AutoSlugField(populate_from='name', always_update='True', unique_with='user')
def __unicode__(self):
return self.name
class Meta:
ordering = ('name',)
class Feed(models.Model):
feed_url = models.URLField(unique=True)
def __unicode__(self):
return self.feed_url
class UserFeed(models.Model):
feed = models.ForeignKey(Feed)
title = models.CharField(max_length=64)
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
slug = AutoSlugField(populate_from='title', always_update='True', unique_with='user')
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
forms
class UserFeedForm(forms.Form):
feed_url = forms.URLField()
title = forms.CharField(max_length=64)
category = forms.ModelChoiceField(Category)
user = forms.HiddenInput()
views
def addfeed(request):
categories = Category.objects.filter(user=request.user)
feeds = Feed.objects.all()
if request.method == 'POST':
form = UserFeedForm(request.POST)
form.fields['category'].queryset = categories
if form.is_valid():
feed = form.save(commit=False)
if form.fields['feed_url'] in feeds:
##### add to Feed object and add reference to UserFeed object
feed.user = request.user
feed.save()
else:
##### get id from Feed object and add reference to UserFeed object
feed.user = request.user
feed.save()
return HttpResponseRedirect("/reader/manage")
else:
form = UserFeedForm()
form.fields['category'].queryset = categories
context = {'form': form,}
return expand_context_and_render(request, context, 'reader/form.html')
Could anybody please point me in the right direction?
thanks
UPDATE
Now I have updated my view and form as follows:
forms
class UserFeedForm(forms.ModelForm):
feed = forms.URLField()
title = forms.CharField(max_length=64)
category = forms.ModelChoiceField(Category)
user = forms.HiddenInput()
class Meta:
model = UserFeed
fields = ['feed', 'title', 'category']
views
def addfeed(request):
categories = Category.objects.filter(user=request.user)
feeds = Feed.objects.all()
if request.method == 'POST':
form = UserFeedForm(request.POST)
form.fields['category'].queryset = categories
if form.is_valid():
feed = form.cleaned_data.get('feed')
if feed in feeds:
##### get id from Feed and add reference to UserFeed
existing_feed = Feed.objects.get(feed_url=feed)
form.feed = existing_feed.id
form.user = request.user
form.save(commit=True)
else:
##### add to Feed object and then add reference to UserFeed object
Feed.object.create(feed_url=feed)
existing_feed = Feed.objects.get(feed_url=feed)
form.feed = existing_feed.id
form.user = request.user
form.save(commit=True)
return HttpResponseRedirect("/reader/manage")
else:
form = UserFeedForm()
form.fields['category'].queryset = categories
context = {'page_title': page_title,
'form': form,
}
return expand_context_and_render(request, context, 'reader/form.html')
I think I'm getting closer but it still won't work, giving me this error:
"Cannot assign "u'http://feeds.bbci.co.uk/news/rss.xml'": "UserFeed.feed" must be a "Feed" instance."
Any idea how can I fix this?
thanks
Try taking a look at this question, and also inline formsets in the docs.
Additionally in your UserFieldForm, feed is defined as a URLField. So in your view, in this line:
feed = form.get_cleaned_data.get('feed')
your feed variable is simply a string, and not actually a Feed object. This is where your error might be occurring? That's where inline formsets could help you. Also, this line also makes your your if feed in feeds: statement moot, because feed is just a string, whereas feeds is a Queryset of all your Feed instances. I think this will cause your view to always end up in the else block?
I'm new to Django myself but I hope this helps you.
The logic you are looking for is:
Check if the feed exists.
If it exists, fetch it.
If it doesn't create a new feed.
Associate the feed with a UserFeed object.
The get_or_create method does parts 1-3, so you need the following:
if form.is_valid():
obj = form.save(commit=False)
feed = form.cleaned_data.get('feed')
feed_obj, created = Feed.objects.get_or_create(feed_url=feed)
obj.feed = feed_obj
obj.save()
return HttpResponseRedirect("/reader/manage")
I finally got it working like this:
forms
class UserFeedForm(forms.Form):
feed = forms.URLField()
title = forms.CharField(max_length=64)
category = forms.ModelChoiceField(Category)
user = forms.HiddenInput()
class Meta:
model = UserFeed
fields = ['feed', 'title', 'category']
views
def addfeed(request):
user = request.user
categories = Category.objects.filter(user=request.user)
if request.method == 'POST':
form = UserFeedForm(request.POST)
form.fields['category'].queryset = categories
if form.is_valid():
feed = form.cleaned_data['feed']
category = form.cleaned_data['category']
title = form.cleaned_data['title']
feed_obj, created = Feed.objects.get_or_create(feed_url=feed)
obj = UserFeed(feed=feed_obj, title=title, category=category, user=user)
obj.save()
return HttpResponseRedirect("/reader/manage")
else:
form = UserFeedForm()
form.fields['category'].queryset = categories
context = {'page_title': page_title,
'form': form,
}
return expand_context_and_render(request, context, 'reader/form.html')
Both tips from the answers below pointed me in the right direction. Thanks !

Django form is only valid after second request

I have a very strange problem with django forms, I display a form which includes an additional formset so that the user can also submit data for a foreign key relation at the same time.
The template always displays a form for the original model and one form for the second model.
I now want to submit the two forms without filling in anything in the second form.
On the first submission the seond form does not validate and the page is redisplayed, but on the second submission the second form is valid! Even so the POST data is identical.
How can this be possible?
Or maybe I am doing this completely wrong, how can you discern if the user did not fill in anything in the formset or if he filled in something invalid?
Here the models:
class Software(models.Model):
creation_date = models.DateTimeField(default=datetime.now)
creator = models.ForeignKey(User)
version = models.CharField(max_length=300, unique=True, editable=False)
major_version = models.IntegerField()
minor_version = models.IntegerField()
[...]
def save(self, **kwargs):
"""
This updates the version string to the combined representation.
"""
self.version = Software.combine_version_string (self.major_version, self.minor_version)
super(Software, self).save(**kwargs)
class SoftwarePatch(models.Model):
file = models.FileField(upload_to='software_patches')
file_name = models.CharField(max_length=255, editable=False)
file_date = models.DateTimeField(default=datetime.now)
upload_date = models.DateTimeField(default=datetime.now)
software = models.ForeignKey('Software', related_name='patches')
firmware_patch = models.BooleanField(default=True)
target_path = models.CharField(max_length=255, blank=True)
class Meta:
unique_together = ('software', 'file_name')
verbose_name_plural = "software patches"
def __unicode__(self):
return self.file_name
def clean(self):
if self.file and not self.file_name:
self.file_name = self.file.file.name
Here my forms:
SoftwarePatchFormSet = inlineformset_factory(Software,
SoftwarePatch,
extra=1)
class SoftwareForm(forms.ModelForm):
"""
A simple form for creating a new software.
"""
class Meta:
model = Software
And finally my view function:
def software_add(request, software_id=None):
if software_id == None:
software = Software()
else:
software = Software.objects.get(id=software_id)
if request.POST:
form = SoftwareForm(request.POST, instance=software)
if form.is_valid():
software = form.save(commit=False)
softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES, instance=software)
if softwarepatch_formset.is_valid():
software = form.save()
softwarepatch_formset.save()
# Redirect, in case of a popup close it
if request.POST.has_key("_popup"):
pk_value = software._get_pk_val()
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
(escape(pk_value), escape(software)))
if 'next' in request.POST:
return HttpResponseRedirect(request.POST['next'])
else:
return HttpResponseRedirect(reverse('index'))
else:
form = SoftwareForm(instance=software)
softwarepatch_formset = SoftwarePatchFormSet(instance=software)
is_popup = request.GET.has_key("_popup") or request.POST.has_key("_popup")
return render_to_response(
'main/software_edit.html',
{'form': form,
'softwarepatch_formset': softwarepatch_formset,
'add': True,
'is_popup': is_popup,
},
context_instance = RequestContext(request)
)
First of all, you should set the instance argument only when creating a form / formset for an existing object i.e. one already in the DB. So for example if software_id = None and it's a GET request, you should only do form = SoftwareForm().
Also, after doing software = form.save(commit=False), you should do software.save() instead of software = form.save(). [I don't think it's really a problem though, just that you're redoing a save]. Remember that if you have a ManyToManyField in the Software model, you need to do form.save_m2m() after software = form.save() as well.
Here's what I think you should have:
def software_add(request, software_id=None):
if request.POST:
if software_id:
software = Software.objects.get(id=software_id)
form = SoftwareForm(request.POST, instance=software)
else:
form = SoftwareForm(request.POST)
if form.is_valid():
software = form.save(commit=False)
softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES, instance=software)
if softwarepatch_formset.is_valid():
software.save()
softwarepatch_formset.save()
# Redirect, in case of a popup close it
if request.POST.has_key("_popup"):
pk_value = software._get_pk_val()
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
# escape() calls force_unicode.
(escape(pk_value), escape(software)))
if 'next' in request.POST:
return HttpResponseRedirect(request.POST['next'])
else:
return HttpResponseRedirect(reverse('index'))
else:
softwarepatch_formset = SoftwarePatchFormSet(request.POST, request.FILES)
else:
if software_id:
software = Software.objects.get(id=software_id)
form = SoftwareForm(instance=software)
softwarepatch_formset = SoftwarePatchFormSet(instance=software)
else:
form = SoftwareForm()
softwarepatch_formset = SoftwarePatchFormSet()
is_popup = request.GET.has_key("_popup") or request.POST.has_key("_popup")
return render_to_response(
'main/software_edit.html',
{'form': form,
'softwarepatch_formset': softwarepatch_formset,
'add': True,
'is_popup': is_popup,
},
context_instance = RequestContext(request)
)
Ok I finally found my problem!
I have the following model field: file_date = models.DateTimeField(default=datetime.now)
This sets the innital-file-date to a value like this: u'2011-10-18 08:14:30.242000'
After being rendered through the html widget the value will be: u'2011-10-18 08:14:30'
So django will think the form was changed and therefore not save.
On the second load django will automatically set the truncated value as initial-file-date and then nothing is changed and the save works as expected.
So now I only have to figure out what to use instead of datetime.now. I will update this post when I have figured it out.