How to display all messages in a conversation style in Django? - django

I'm trying to make a simple messaging feature for my web app. I have the following views for creating and listing conversations i.e. groups of messages between two users.
views.py
class CreateMessageView(CreateView):
model = Message
fields = ['msg']
def form_valid(self, form):
form.instance.frm = frm = self.request.user
form.instance.to = to = User.objects.get(id=self.kwargs['pk'])
form.instance.thread = "%s & %s" % (frm, to) if int(frm.id) < int(to.id) else "%s & %s" % (to, frm)
return super(CreateMessageView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(CreateMessageView, self).get_context_data(**kwargs)
context['to'] = User.objects.get(id=self.kwargs['pk']).get_full_name()
return context
def get_success_url(self):
return reverse('main:message-list')
class ListMessageView(ListView):
model = Message
def get_queryset(self):
return Message.objects.filter(Q(to=self.request.user) | Q(frm=self.request.user)).order_by('-created', 'thread')
message_list.html
<h3>Messages</h3>
{% regroup object_list by thread as threads %}
<ul>
{% for item in threads %}
<li> {{ item.grouper }} </li>
{% empty %}
<li>No messages to show. Send a message to someone today!</li>
{% endfor %}
</ul>
I am trying to use the regroup feature to group messages with the same conversation name. However, this shows the conversation name on the list, but I want to show the name of the person on the other end of the conversation. How can I achieve this?

Related

How to perform addition (+) of attributes from multiple model instances connected via one ManytoManyField in Django?

Problem Statement: I want to add up the Activity.models attribute net_cost from within Trip.models i.e. connected via ManytoManyField. I have 3 or more choices per instance, so add: from the template language is inadequate (https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#add)
More Context: Activity is connected to Trip via ManytoManyField. Accessing and adding up via save method in models.py is causing id needs to be assigned first error, I believe since ManytoMany can only be assigned once an instance of the model is saved.
Even if I access them all in the views.py before rendering, the context passed even after iterating over each object in list, can only repeat a common context["grand_total"] for all entries rendered in the template, whereas I need grandtotal for each Trip instance in frontend List.view.
Models:
Activity ->
class Activity(models.Model):
activity_location = [
...
]
acitivity_duration = [
...
]
activity_title = models.CharField(max_length=20, unique=True)
...
net_cost = models.PositiveIntegerField(validators=[MaxValueValidator(100000), MinValueValidator(0)], default=0)
class Meta:
ordering = ['-margin']
def __repr__(self):
return f"{self.activity_title} - {self.activity_location} - {self.net_cost}"
def __str__(self):
return f"Activity {self.activity_title}, Location {self.activity_location}, Duration {self.acitivity_duration}, Cost {self.net_cost}"
def get_absolute_url(self):
return reverse('activitys-list')
Trip ->
class Trip(models.Model):
transfer_choices = [
...
]
transfers = models.CharField(max_length=11, choices=transfer_choices, blank=True, null=True)
activity = models.ManyToManyField(Activity, related_name="activities", blank=True, verbose_name='Activities', help_text='select multiple, note location tags')
....
class Meta:
ordering = ['-entry_created']
def __repr__(self):
return f'{self.customer.name} for {self.duration} day/s'
def __str__(self):
return f"{self.customer.name} - {self.duration} - {self.start_date} - {self.end_date}"
def save(self, *args, **kwargs):
...
#successfully processing all inhenrent model attributes for grand total, but
unable to process activity.net_cost for all entries.
super(Trip, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('trip-lists')
View:
class TripLists(LoginRequiredMixin, ListView):
login_url = '/login/'
redirect_field_name = 'index'
model = Trip
template_name = 'gobasic/trip_list.html'
paginate_by = 10
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in extra QuerySets here
Trips = Trip.objects.all()
activity_total = 0
for i in Trips:
for a in i.activity:
activity_total += a.net_cost
# Is pointless as same gets repeated.
context['activity_total'] = activity_total
context['total_trips'] = Trip.objects.all().count()
return context
Template:
<h2>Trip List - {{total_trips}}</h2>
<p><button type="button" class="btn btn-info">Add Trip</button>
<a href="{% url 'index' %}"> <button type="button" class="btn btn-info">Home</button>
</a></p>
<ol>
{% for t in object_list %}
<li><strong>🆔:</strong> {{ t.customer.name}}<strong> PB:</strong> {{ t.hotel_pb.hotel_name }} <strong>HV:</strong> {{ t.hotel_hv.hotel_name }} <strong>NL:</strong> {{ t.hotel_nl.hotel_name }} <!--<strong>Activities:</strong> {% for activity in t.activity.all %} {{ activity.activity_title}}, {% endfor %}--> <strong>Start:</strong>{{t.start_date.date}} <strong>End:</strong>{{t.end_date.date}} <strong>🏨:</strong>{{t.hotel_cost}} 🚕 {{t.transfer_cost}} | 🤿 {% for act in t.activity.all %} {{ act.net_cost}} {% endfor %}<strong> Grand Total= {{t.hotel_cost |add:t.transfer_cost}}</strong> <button class="btn">🖋️</button> </li>
<br>
{% empty %}
<p>No Entries Detected Yet !</p>
{% endfor %}
</ol>
How do I get a grand total i.e. t.hotel_cost + t.transfer_cost + t.activity.net_cost (for all objects within activity i.e. ManytoMany inside object_list )

Django Paginate_by not displaying proper pagination

Hello i have a page using paginate_by 10, and instead i'm getting only 9 elements per page, even tho in the element inspector i see 10 grid spaces my for cicle is just being able to fill 9 out of 10 then it goes into the next page.
Views.py
class VideoListView(generic.ListView):
model = Video
template_name = 'index.html'
context_object_name = 'video_list'
paginate_by = 10
def get_queryset(self):
return Video.objects.order_by('-date')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
return context
EDIT AFTER A QUESTION. here's the template logic
<ul id="first-category" class="items-container">
{% for video in video_list %}
{% if video.home ==True %}
<li class="item">
</li>
{% endif %} {% endfor %}
As possible causes i did find that when i do "paginate by 1" the page starts displaying empty pages for the pages that aren't considered in the IF. which makes me think that the if statement is taking into consideration the empty videos even tho they aren't listed.
How can i fix this?
i do want to filter the videos that aren't meant for the home_page
Thanks a lot in advance for the reply
The reason this happens is because you filter in the template, so after the queryset is paginated. Filtering in the template is not a good idea, for example because it will render the pagination incorrect, but it is also inefficent.
You should filter in the view, with:
class VideoListView(generic.ListView):
model = Video
queryset = Video.objects.filter(home=True).order_by('-date')
template_name = 'index.html'
context_object_name = 'video_list'
paginate_by = 10
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['category_list'] = Category.objects.all()
return context
and thus remove the {% if video.home == True %} … {% endif %} part.

Using "in" operator in Django template not working as expected

Could really use help figuring out what I'm misunderstanding about the following: I'm using the built-in "in" operator to check if a user (standard user model) is in a queryset of Board members.
This is the Board member model:
class BoardMembers(models.Model):
board = models.ForeignKey(Board, related_name="memberships", blank=True)
user = models.ForeignKey(User,related_name='user_boards', blank=True)
member_role = models.CharField(choices=MEMBER_ROLES, max_length=100, blank=True)
def __str__(self):
return self.user.username
Here is the view I'm using:
class ViewBoard(SelectRelatedMixin, generic.DetailView):
model = models.Board
select_related = ("user",)
template_name = 'board/view_board.html'
def get_context_data(self, **kwargs):
context = super(ViewBoard, self).get_context_data(**kwargs)
context['boardmembers_list'] = BoardMembers.objects.filter(board__slug=self.kwargs['slug'])
return context
And this is the html part I'm struggling with:
{% if user in boardmembers_list %}
<h1>HEY, {{ user.username }}, YOU'RE ALREADY A MEMBER!</h1>
{% else %}
<a class="btn btn-primary" href="{% url 'board:join_board' slug=board.slug pk=board.pk %}">Join this Board</a>
{% endif %}
I tested to see if individually the objects could be retrieved in the template and both the user and the boardmembers_list show up correctly.
From the docs it looks like this should be a relatively straightforward thing to do. Can anyone tell me where I'm going wrong or what I'm misunderstanding about how these operators work?
I think its better to decide if the user is in board or not in the View, instead of template.
class ViewBoard(SelectRelatedMixin, generic.DetailView):
...
def get_context_data(self, **kwargs):
board_mem_list = BoardMembers.objects.filter(board__slug=self.kwargs['slug'])
context = super(ViewBoard, self).get_context_data(**kwargs)
context['boardmembers_list'] = board_mem_list
context['user_in_board'] = board_mem_list.filter(user=self.request.user).exists() # this will check if user is already in boardmemberlist
return context
In template:
{% if user_in_board %}
<h1>HEY, {{ user.username }}, YOU'RE ALREADY A MEMBER!</h1>
{% else %}
<a class="btn btn-primary" href="{% url 'board:join_board' slug=board.slug pk=board.pk %}">Join this Board</a>
{% endif %}
And you are trying to search userin boardmemebers_list, and it won't work because user is a User model instance and boardmemebers_list is a queryset of BoardMember model where user is just a field of that model.

How to overide django modelform to achieve custom behaviour

I have an Item object that has a manytomany relation to another object Option. I create a modelform from the Item object like so;
class Item(models.Model):
category = models.ForeignKey(Category)
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
options = models.ManyToManyField(Option)
class OptionForm(ModelForm):
options = forms.ChoiceField(widget=forms.RadioSelect())
class Meta:
model = Item
fields = ( 'options', )
When i render the form in the template it renders all available options for the Item object(the expected behavior) even those not created by a specific item. I want to be able to load options defined by the specific Item that will be chosen by the user. How do i override the form to achieve such behavior.for example without a form i can render an Items own Options through its id. item = Item.objects.get(pk=id)
Its tough to make ModelForm's defined on the fly because they are intimately tied to the structure in your model. Nevertheless you could use some clever template control flow and view rendering to get your desired effect. This is untested so you millage with this might vary.
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
<ul>
{% if user_option.category %}
<li>{{ form.caregory }}</li>
{% endif %}
{% if user_option.name %}
<li>{{ form.name }}</li>
{% endif %}
{% if user_option.p_opt %}
<li>{{ form.price }}</li>
<li>{{ form.options }}</li>
{% endif %}
</ul>
{% endfor %}
</form>
From the Djano docs here.
Try overriding the form's init method and passing in the Item pk as an additional argument. The trick here is to pop the argument before calling the parent init.
class ItemOptionsForm(forms.ModelForm):
class Meta:
model = Item
def __init__(self, *args, **kwargs):
# pop 'item_id' as parent's init is not expecting it
item_id = kwargs.pop('item_id', None)
# now it's safe to call the parent init
super(ItemOptionsForm, self).__init__(*args, **kwargs)
# Limit options to only the item's options
if item_id:
try:
item = Item.objects.get(id=item_id)
except:
raise ValidationError('No item found!')
self.fields['options'] = forms.ChoiceField(item.options)
Then, in your view, create the form like:
form = ItemOptionsForm(item_id=item_id)
The nice thing about this is that you can raise ValidationErrors that will show up in the form.
Be aware that this doesn't prevent someone from POSTing option IDs to your form which do not belong to the Item, so you'll likely want to override the ModelForm.clean() to validate the options as well.
learning from link django: How to limit field choices in formset? provided by #jingo, i solved the problem by first of all creating dynamic form like so;
def partial_order_item_form(item):
"""dynamic form limiting optional_items to their items"""
class PartialOrderItemform(forms.Form):
quantity = forms.IntegerField(widget=forms.TextInput(attrs={'size':'2', 'class':'quantity','maxlength':'5'}))
option = forms.ModelChoiceField(queryset=OptionalItems.objects.filter(item=item),widget= forms.RadioSelect())
return PartialOrderItemform
then validating form like so;
def show_item(request,id):
option = get_object_or_404(Item,pk=id)
if request.method == 'POST':
form = partial_order_item_form(option)
#bound form to POST data,
final_form = form(request.POST)
# check validation of posted data
if final_form.is_valid():
order.add_to_order(request)
url =urlresolvers.reverse('order_index',kwargs={'id':a.id})
# redirect to order page
return HttpResponseRedirect(url)
else:
form = partial_order_item_form(item=id)
context={
'form':form,
}
return render_to_response('item.html',context,context_instance=RequestContext(request))

CharField values disappearing after save (readonly field)

I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.
Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)
models.py
class Grade1(models.Model):
student = models.CharField(max_length=50, unique=True)
finalGrade = models.CharField(max_length=3)
class Grade1OForm(ModelForm):
student = forms.CharField(max_length=50, required=False)
def __init__(self, *args, **kwargs):
super(Grade1OForm,self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['student'].widget.attrs['readonly'] = True
self.fields['student'].widget.attrs['disabled'] = 'disabled'
def clean_student(self):
instance = getattr(self,'instance',None)
if instance:
return instance.student
else:
return self.cleaned_data.get('student',None)
class Meta:
model=Grade1
views.py
from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
studentQueryset = Grade1.objects.all()
if request.method=='POST':
myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
if myGradeFormSet.is_valid():
myGradeFormSet.save()
info = "successfully modified"
else:
myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
return render_to_response('grades/modifyAllGrades.html',locals())
template
<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
{# myform.as_table #}
<tr>
{% for field in myform %}
<td> {{ field }} {{ field.errors }} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
Your way of displaying the readonly field is the problem.
Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.
That is why ReadOnly Widget has to be more complex than just being a html disabled field.
Try using a real ReadOnlyWidget, one that overrides _has_changed.
Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.
class ReadOnlyWidget(forms.Widget):
def __init__(self, original_value, display_value=None):
self.original_value = original_value
if display_value:
self.display_value = display_value
super(ReadOnlyWidget, self).__init__()
def _has_changed(self, initial, data):
return False
def render(self, name, value, attrs=None):
if self.display_value is not None:
return unicode(self.display_value)
return unicode(self.original_value)
def value_from_datadict(self, data, files, name):
return self.original_value
I'm stretching myself a little here, so some thoughts:
% Have you sniffed the traffic to see exactly what's being sent between browser and server?
% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?
% Have you looked at the source of your HTML after Python parses it?