I'm building a tool, that gets user timeline tweets and their responses and I need to find out how to store those conversations in a database to render them afterwards in a template.
To get a conversation, I use a while loop, that gets 'in_reply_to_status_id' of a tweet, then retrieves the status object of this reply by the id, finds its 'in_reply_to_status_id' etc till the whole conversation gets retrieved.
This is the code I use:
conversation = []
while True:
response = api.get_status(id=tweet_id, tweet_mode='extended')._json
if response['in_reply_to_status_id'] == None:
break
else:
message_dict = {}
message_dict['author'] = response['user']['screen_name']
message_dict['text'] = response['full_text']
message_dict['created_at'] = response['created_at']
message_dict['author_profile_url'] = response['user']['profile_image_url']
conversation.append(message_dict)
tweet_id = response['in_reply_to_status_id']
if len(conversation) == 0:
return None
return reversed(conversation)
In my models.py I have a Tweet model and I need to find out how to make a model that stores the whole conversation/dialogue/thread retrieved by the script above. Also, it should be possible to render the conversation as a simple chat dialogue afterward.
My first thought was to add "replies" field to my Tweet model and store the conversation as a JSON but this doesn't look like the right solution to me.
I don't know all the fields that you get or want to store, but for what I saw at your code this should work (set the max_length at what it should do, I don't know):
Tweet(models.Model):
author = models.Charfield(max_length=50)
text = models.Charfield(max_length=500)
author_profile_url = models.URLField(null=True, blank=True)
reply_to = models.ForeignKey(Tweet, on_delete=models.CASCADE, related_name='replies')
creation_date = models.DateTimeField()
To print all the conversation you will need to iterate the FK looking for all the related objects and order them by creation_date.
If what you want is to show only one conversation you should send through the view the initial object of the conversation and then you could do something like:
{{ tweet.author }}
{{ tweet.text }}
{{ tweet.creation_date }}
{% if tweet.reply_to_set.count > 0 %}
{% with replies=tweet.reply_to_set.all %}
{% for reply in replies %}
{{ reply.author }}
{{ reply.text }}
{{ reply.creation_date }}
replies to this message: {{ reply.reply_to_set.count }}
{% endfor %}
{% endwith %}
{% endif %}
This will show first the tweet information, then check if there are replies and if there are show you the information of each one. Inside that reply I added the replies to this message in which you can let the people know if that reply have other replies. Just in case you want to do infinite reply system (like twitter). But then there you could add a link to this same template in which the object is that reply, so that will be the main tweet and then the replies will be shown.
Related
I apologize in advance if this has already been asked, but I couldn't find any answers that answered the problem I'm having:
I need to do something similar to a For...Else loop in a Django template.
I need to show a button on a template base on an if condition:
If the user has already bought this product, then show button 1
If the user has not bought this product, then show button 2
For each product, I have to go through the user's purchases, and then show one button or the other depending on whether they have already bought the product or not.
A simplified (and wrong) version of the code would be like:
{% for product in products %}
//other code here
{% for purchase in purchases %}
{% if purchase.service_id.id == product.id %}
// show button 1
{% else %}
// show button 2
{% endif %}
{% endfor %}
{% endfor %}
However, this code doesn't work, as it shows both buttons as it goes through the for loop.
I can't do a For...Empty because the user may have other purchases (so the for loop wouldn't be empty), but none of them coincide with this product.
Thanks in advance.
EDIT:
Thanks #JashOFHop for the reply! In the end, I found a way around. I'll share it in case anyone else find themselves in this situation:
For clarity, the models concerned in this case are:
class User(AbstractUser):
pass
class Service(models.Model):
user_id = models.ForeignKey("User", on_delete=models.CASCADE, related_name="user_id_services")
name = models.CharField(max_length=64)
status = models.BooleanField(default=True)
description = models.TextField(max_length=300)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category_name")
price = models.IntegerField()
slots = models.IntegerField(default=1)
amount = models.IntegerField(default=1)
watchedby = models.ManyToManyField(User, blank=True, related_name="watchedby")
class Purchase(models.Model):
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_id_purchases")
service_id = models.ForeignKey(Service, on_delete=models.CASCADE, related_name="service_id_purchases")
amountpaid = models.IntegerField()
The view for this template was:
def explore(request):
# get all the active services from the db
services = Service.objects.filter(status=True).order_by('name')
# get the catogories for the filter option
categories = Category.objects.all().order_by('category')
# get the info of the user
userinfo = User.objects.get(username=request.user)
# get the info of the user's purchases
purchases = Purchase.objects.filter(user_id=userinfo)
# render the template
return render(request, "barter/explore.html", {
"services": services,
"categories": categories,
"userinfo": userinfo,
"purchases": purchases
})
And the template, as explained above, rendered all the services and in each is supposed to check whether this user has already bought said service or not.
Solution:
In the view I've added this and passed it also to the template:
# create a list of the IDs of the services purchased by the user to be able to render the buy/bought button correctly
purchases_list = []
for purchase in purchases:
purchases_list.append(purchase.service_id.id)
Then, the template is:
{% for service in services %}
// other code with infomation of the service here
// Important part:
{% if service.id in purchases_list %}
<button>You already bought this service</button>
{% else %}
<button>Buy now</button>
{% endif %}
{% endfor %}
You can create another models, for example Profile
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
cart = models.MenyToManyField(Product, null=True)
so in views for byu button you can write like this
def buy(request, pk): #byu button
product = Product.objects.get(id=pk)
profile = Profile.objects.filter(user.username=request.user)
profile.cart.add(product.id)
return redirect('some html')
another button has the same
def index(request, pk): # for template
product = Product.objects.get(id=pk)
profile = Profile.objects.filter(user.username=request.user)
return response(request, 'some html', context)
so template
{% for i in product%}
{% for products in profile%}
{% for p in products.cart.all %}
{% if i.id in p.id%}
<p>you bought</p>
{% else %}
<a href='byu'>Buy</a>
{% endif %}
{% endfor%}
{% endfor%}
{% endfor%}
but i think there is another the easiest way to solve your problem
I have an object field that stores a list. The Django template is treating the list like a string, it iterates over each character, not each list value.
Tried saving the object field in various ways, "['1','2']" and "1,2". Tried the "|make_list" filter.
models.py
class SpecialField(models.Model):
name = models.CharField(max_length=200,blank=True,null=True)
description = models.CharField(max_length=200,blank=True,null=True)
value_options = models.CharField(max_length=200,blank=True,null=True)
# value_options might be "['1','2']" or "red, green, blue"
views.py
object_field_list= SpecialField.objects.all()
context = {
'object_field_list': object_field_list,
}
return render(request, 'app1/template-detail.html', context)
template
{% for object_field in object_field_list%}
{% for list_value in object_field.value_options %}
<option>{{ list_value }}</option>
{% endfor %}
{% endfor %}
I was hoping for:
<option>1</option>
<option>2</option>
But I am getting:
<option>[</option>
<option>'</option>
<option>1</option>
<option>'</option>
<option>,</option>
<option>'</option>
<option>2</option>
<option>'</option>
<option>]</option>
You are wanting to show data from a model, so let's suppose you have a model
class SpecialField(models.Model):
name=models.CharField(max_length=101)
price = models.IntegerField(default=0)
Given the way you've set in in context you can show this in the template with
{% for obj in object_field_list %}
{{ obj.name}} - {{ obj.price }}
{% endfor %}
Obviously, you need to amend for your model
Tweak on your suggestion...it needs one more level of looping. Still can't get it to work.
models.py
class SpecialField(models.Model):
name=models.CharField(max_length=101)
price = models.IntegerField(default=0)
custom_list = models.CharField(max_length=200)
template
{% for obj in object_field_list %}
{{ obj.name}} - {{ obj.price }}
{% for list_value in obj.custom_list %}
{{ list_value }}
# this is where it's breaking for me
{% endfor %}
{% endfor %}
context = {
'object_field_list': SpecialField.objects.values_list('value_options', flat=True),
}
Should get you what you actually want to loop over.
EDIT: Sorry, I missed the fact you are storing a string rather than using an ArrayField or similar. The problem from your updated answer is the data you have isn't consistent. If it were simply all comma-separated values you could do:
object_field_list = [value_list.split(',') for value_list in SpecialField.objects.values_list('value_options', flat=True)]
but you will need some way of normalizing the data you're storing in value_options. How does the data get into the database? If it's via Django, you can apply some kind of cleaning method on the form or API endpoint that accepts the data.
Tried saving the object field in various ways, "['1','2']" and "1,2". Tried the "|make_list" filter.
If you have complete control over the incoming data, you would be better off normalizing the data: rather than storing a single value_options entry on SpecialField, you would remove that field and add a second model, e.g., SpecialFieldOption like
class SpecialFieldOption(models.Model):
name = models.CharField(max_length=200, blank=False)
field = models.ForeignKey(SpecialField, related_name='options')
# now you can do
SpecialField.objects.get(pk=1).options.all()
to loop over the list of all options for a given SpecialField.
This was the handiest solution...define a new list using split. https://stackoverflow.com/a/8318915/9268133. Thanks for everyone's help!
Intro: I have a 3 models user, post, group. User is able to make posts however each post has to belong to a group. I have 400 fixed groups. Users have to choose from the existing 400 groups for their posts. Users cannot add, delete, update group's.
Furthermore:
Users can become a member of groups and when they click on a certain group. They see all the posts in that group.
Users can follow-unfollow other users.
**What I have right now:**When a user signs-in. In his home page he sees. All the posts of the each individual group he is a member of. When all the posts from all the groups are done with. He then sees the posts of all the people he follows one by one
What I want: I want the posts to be arranged by time
Example: If one of the people he follows just wrote a post then that post is first. Chronologically the second post was in one of the groups that he is a member of. That post comes second... You get the idea
Below are my models
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
group = models.ForeignKey(Group, related_name='posts')
title = models.CharField(max_length=250, unique=True)
message = models.TextField()
created_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created_at']
class Group(models.Model):
name = models.CharField(max_length=250, unique=True)
members = models.ManyToManyField(User, through='GroupMember')
my views.py
class Homepage(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super(Homepage, self).get_context_data(**kwargs)
posts_of_people_i_follow = []
posts_from_groups_that_i_member = []
if self.request.user.is_authenticated():
my = self.request.user
for user in my.following.all():
posts_of_people_i_follow += user.posts.all()
posts_of_people_i_follow.save()
for group in my.group_set.all():
posts_from_groups_that_i_member += group.posts.all()
posts_from_groups_that_i_member.save()
context['posts_of_people_I_follow_list'] = posts_of_people_i_follow
context['posts_from_groups_that_i_member'] = posts_from_groups_that_i_member
return context
In my Templates I currently have
{% for post in posts_from_groups_that_i_member %}
{{ post.title }}
{{ post.message }}
{% endfor %}
{% for post in posts_of_people_I_follow_list %}
{{ post.title }}
{{ post.message }}
{% endfor %}
Option 2: After breaking my head on this for a while I have been trying a different approach. But this looks really expensive
In the user profile model
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def get_all_posts(self):
my = self.user
all_posts_for_this_user = []
for user in my.following.all():
all_posts_for_this_user += user.posts.all()
all_posts_for_this_user.save()
for group in my.group_set.all():
all_posts_for_this_user += group.posts.all()
all_posts_for_this_user.save()
return all_posts_for_this_user
Then in my templates
{% for post in user.profile.get_all_posts %}
{{ post.title }}
{{ post.message }}
{% endfor %}
Can anyone think of a better solution. Or is this ok
if I understand it correctly, you need post date in your Post model then you can order posts by Post.objects.ordey_by('post_date')
https://docs.djangoproject.com/en/2.1/ref/models/querysets/#django.db.models.query.QuerySet.order_by
Modify your view class by using an 'order_by()' method on the queryset that fetches your post objects and filters them according to when they were created (The 'created_at' field comes in handy here).
Add this to your 'Homepage' view class:
def get_queryset(self):
queryset_list = Post.objects.order_by('-created_at')
Further reading can be found here:
https://docs.djangoproject.com/en/2.1/ref/models/querysets/
EDIT
I think the template rendering issue deserves its own question?
In your template, try this:
{% for post in posts_from_groups_that_i_member %}
{% if post in posts_of_people_I_follow_list %}
{{ post.title }}
{{ post.message }}
{% endfor %}
{% endfor %}
In terms of performance, it may not be the best option but it should work. Let me know your findings.
I'm trying to make a search function that allows me to enter the value of an object in an instance so I can display that instance and several of it's objects on the same page as the search page. Here's what I have so far:
#models.py
class Student(models.Model):
# STEP 1 BASIC INFO
student_id = models.CharField(max_length=128, unique=True)
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
ssn = USSocialSecurityNumberField(null=False)
gender = models.CharField(max_length=128, choices=GENDER_CHOICES)
dob = models.DateField(auto_now=False, auto_now_add=False, db_column="date of birth")
contact_number = models.CharField(max_length=128)
address = models.CharField(max_length=128)
city = models.CharField(max_length=128)
state = USStateField(choices=STATE_CHOICES, default='NJ')
zipcode = USZipCodeField(blank=True)
country = CountryField(default='US', blank=True)
home_phone = models.CharField(max_length=128)
cell_phone = models.CharField(max_length=128)
email = models.EmailField(max_length=254, validators=[validate_email])
def __str__(self):
return self.first_name + self.last_name
#views.py
def search_Student(request):
context_dict = {}
if request.method == 'POST':
query = request.POST['last_name_search']
results = Student.objects.filter(last_name=query)
if query:
context_dict['results'] = results
else:
context_dict['no_results'] = query
return render(request, "students/search_student.html", context_dict)
#search_student.html
{% block main_content %}
<form method="post" action="/students/search_student/">
{% csrf_token %}
<label for="last_name_search">Last Name:</label>
<input type="text" name="last_name_search" id="last_name_search">
<input type="submit" name="submit">
</form>
<div id="result_panel">
{% if no_results %}
No results returned for <q>{{ no_results }}</q>
{% else %}
{% for result in results %}
{{ result.last_name }}
{% endfor %}
{% endif %}
</div>
{% endblock %}
#urls.py
urlpatterns = [
url(r'^$', students_views.Students, name='students'),
url(r'^add_student/$', students_views.add_Student, name='add_student'),
url(r'^id=(?P<identify>[\w]+)/add_studentcourse/$', students_views.add_StudentCourse, name='add_studentcourse'),
url(r'^id=(?P<identify>[\w]+)/add_studentemployment/$', students_views.add_StudentEmployment, name='add_studentemployment'),
url(r'test/$', students_views.test, name='test'),
#URL for the search page.
url(r'^search_student/$', students_views.search_Student, name='search_student'),
url(r'^current_student/$', students_views.current_Student, name='current_student'),
url(r'^all_my_student/$', students_views.all_My_Student, name='all_my_student'),
url(r'^public_student/$', students_views.public_Student, name='public_student'),
url(r'^sales_reports/$', students_views.sales_Reports, name='sales_reports'),
url(r'^switch_counselor/$', students_views.switch_Counselor, name='switch_counselor'),
url(r'^source_admin/$', students_views.source_Admin, name='source_admin'),
url(r'^super_tool/$', students_views.super_Tool, name='super_tool'),
url(r'^help_and_settings/$', students_views.help_And_Settings, name='help_and_settings'),
]
Basically, if I type in a last name in the input, I want it to be able to grab all model instances that have a last name equal to that, and to be able to put any information I want about all the matching instances, like the name, gender, and address on the exact same page as the search. The POST method may be confusing, but someone insists that I do this (It didn't work when I changed the method to GET either). Could someone please point out any errors or missing pieces in my code? Thanks.
Edit: Added the urls.py.
How to debug this
Remove the action from the tag. The browser will automatically post to the same URL. This rules out any issues with redirects, mappings or upstream urlconfs.
Use View Source in the browser to make sure nothing is returned, as opposed to the div being hidden by CSS or JS.
Make sure results are found:
views.py
if query:
results = Student.objects.filter(last_name=query)
if results.count():
context_dict['results'] = results
else:
context_dict['no_results'] = query
template
<div id="result_panel">
{% if no_results %}
No results returned for <q>{{ no_results }}</q>
{% else %}
{% for result in results %}
{{ result.last_name }}
{% endfor %}
{% endif %}
</div>
Note that the above cannot ever be empty. It should always show something. If it does not, it is a display error and the data is there. See point 2.
If it shows the "no results" part, then try to replicate the query in the django shell:
python manage.py shell
>>> from yourapp.models import Student
>>> Student.objects.filter(last_name='what you typed')
>>> Student.objects.filter(last_name__iexact='what you typed')
>>> Student.objects.filter(last_name__icontains='what you typed')
>>> Student.objects.count()
If none of the first three returns results, then you made a typo or number 4 will show you have no students.
SO won't let me comment quite yet, so here to say that you can indeed (and are) supposed to treat dicts like a list in django templating seen here.
Can you please post your error? I'm not sure I'm seeing whats wrong. Your method adapts over to my code just fine, so I'm a bit lost.
Editing for formatting:
You should maybe make a compile time dictionary, IE:
context_dict = {
"results": results
}
return render(request, "students/search_student.html", context_dict)
Make sure to return in the if, so the scope is in the proper place, and if your if finds nothing return a variable saying so.
If you're bent on sticking to your answer, trying referencing it in the template as so..
{% for result in results.results %}
Your dictionary has a results entry, that points to the variable results. I believe you are iterating through the dictionary in your current example, as opposed to all the queries.
Of course you can use a view method and try to find the error in your custom search and template code.
OR you can do it in a more Django way, relying on pre-built features of Django:
Use Django's ListView to query on and display the Student Model objects. You get paging, error handling, context setup for free with this. See other StackOverflow questions like https://stackoverflow.com/a/33350839/621690 for example code.
Your code that filters the Students would go into get_queryset - or you can use django-filter.
For nice usability, you can add Select2 to your form input which allows autocomplete/lookahead. See django-select2
Here are my models:
class Activity(models.Model):
title = models.CharField(blank=False, max_length=100)
description = models.TextField(blank=False)
class UserActivityWork(models.Model):
activity = models.ForeignKey(Activity)
user = models.ForeignKey(User)
hours_worked = models.FloatField()
comment = models.TextField()
Example data would be, an Activity of "climbing Mt Everest" and each user would be able to input how long it took them and a comment.
Here's my question: How can I display a list of all the Activities, and if the user has entered data for that Activity, display the pertinent details next to the Activity?
So far, I have considered:
creating a dictionary of
UserActivityWork with a key of the Activity id and a value of the user's UserActivityWork. This would be fine with
me, but I have no idea of how to do
this in django's templating system (ie, how do you say: {{ user_work[activity.id] }})
creating an object that would hold
both the Activity and
UserActivityWork. I haven't done this
one, because I am hoping that django
has a better way to do this.
Any insight would be greatly appreciated!
Assuming you have 2 querysets accessable from within your template (say as activities and user_activities)
A naive way would be to iterate over each activity and then over each user activity.
{% for activity in activities %}
{{ activity.title }}
{% for user_activity in user_activities %}
{% ifequal user_activity.activity activity %}
Display userdata
{% endifequal %}
{% endfor %}
{% endfor %}
Dictionary lookups can be performed in templates by using a dot (.)
Technically, when the template system encounters a dot, it tries the following lookups, in this order:
Dictionary lookup
Attribute lookup
Method call
List-index lookup
Another option would be to create a custom template tag. You could loop over the activity list as before and then pass the activity and either the user_activity list or the user to the tag to perform the lookup and render the required data.
Thanks for the hint, Gerry. I found that writing a custom template tag as you suggested was the way to go.
Here are the gory details, in case anyone stumbles across this.
In the view method, I published a dictionary "user_activity_status" which contains a key of activity.id and value of UserActivityWork object for the logged in user's work on that activity
This is the the relevant section of the template. Basically this going to add a variable "map_value" with a value of
getattr(user_activity_status[activity.id], "comment")
Here's the template:
{% load *file-name-of-the-templatetag-file* %}
{% access_map_method user_activity_status activity.id comment %}
{% if map_value %}
{{ map_value }}
{% else %}
get working sucka!
{% endif %}
here is the section of the templatetag file (see Gerry's links for the details of how to set this up)
from django import template
register = template.Library()
#register.tag(name="access_map_method")
def do_access_map_method(parser, token):
try:
tag_name, dict_name , key_name, method_name = token.contents.split()
except ValueError:
msg = '%r tag requires three arguments' % token.contents[0]
raise template.TemplateSyntaxError(msg)
return MapNode(dict_name , key_name, method_name)
class MapNode(template.Node):
def __init__(self, dict_name, key_name, method_name):
self.dict_var = template.Variable(dict_name)
self.key_var = template.Variable(key_name)
self.method_name = method_name
def render(self, context):
try:
dict_obj = self.dict_var.resolve(context)
key_obj = self.key_var.resolve(context)
if key_obj in dict_obj.keys():
if self.method_name:
context['map_value'] = getattr(dict_obj[key_obj], self.method_name)
else:
context['map_value'] = dict_obj[key_obj]
else:
context['map_value'] = ''
except template.VariableDoesNotExist:
context['map_value'] = ''
return ''