Over the last few days I have been working on building a Django web application for allowing users to make queries on a MLB Statistics database. I have some of the web app working fine, however I tried to implement a search function and ran into problems with the URL mapping. I tried to move on from this and instead work on getting the database to display information based on the link to the player that they click on, but I am running into the same issue with the URL's being dynamic based on the player's player_id. This portion only uses the People model which has player_id as its primary key.
Currently, I have an 'allPlayers.html' that lists every player in the database along with a link sending the player's player_id to the url as so:
allPlayers.html
{% extends "base_template.html" %}
{% block content %}
<h1>MLB Stats All Players</h1>
<p>Currently, there are : {{ num_players }} players listed in the database.</p>
<table style="width:100%">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th></th>
</tr>
{% for player in players %}
<tr>
<td>{{ player.name_first }}</td>
<td>{{ player.name_last }}</td>
<td>More on {{ player.name_first }} {{ player.name_last }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
And the corresponding view for 'allPlayers.html':
def allplayers(request):
"""View function for allPlayers page of site."""
# Generate counts of people object
num_players = People.objects.count()
players = People.objects.all()
context = {
'num_players': num_players,
'players': players,
}
# Render the HTML template allPlayers.html w/ data in context variable
return render(request, 'allPlayers.html', context=context)
With just the above I have successfully created a page on my web app that lists each player in the database along with a link, and notice that I am trying to send the player_id through <a href='players/{{ player.player_id }}>. Currently the allPlayers portion works fine. However, things go south when I add the following playerInfo.html and corresponding view:
playerInfo.html
{% extends "base_template.html" %}
{% block content %}
{% if results %}
{% for player in results %}
<p>{{ player.name_first }} {{ player.name_last }}</p>
{% endfor %}
{% endif %}
{% endblock %}
And the view:
def player(request, pk=None):
if pk is not None:
print('Hello world')
print('pk :', pk)
#instance = get_object_or_404(People, player_id=pk)
results = People.object.filter(player_id=pk)
context = {
"results": results
}
return render(request, "playerInfo.html", context)
else:
print('Hello')
return render(request, 'playerInfo.html')
My idea was that the link noted earlier containing {{ player.player_id }} would match up with the following url and place the player_id value in for pk, as follows using the <int:pk> syntax instead of regex:
polls/urls.py
urlpatterns = [
path('', views.index, name='index'),
path('allPlayers', views.allplayers, name='allplayers'),
path('allTeams', views.allteams, name='allteams'),
path('search/', views.search, name='search'),
path('player/<int:pk>/', views.player, name='player'),
]
However, once I navigate to my 'allPlayers' page and click on one of the links for the player, say Hank Aaron (who has player_id aaronha01), I get the following Page not found (404) error:
Using the URLconf defined in baseballdb.urls, Django tried these URL patterns, in this order:
polls/ [name='index']
polls/ allPlayers [name='allplayers']
polls/ allTeams [name='allteams']
polls/ search/ [name='search']
polls/ player/<int:pk>/ [name='player']
admin/
The current path, polls/player/aaronha01/, didn't match any of these.
I have been having trouble with this for quite a bit now. If anyone has any advice and can point me in the right direction of how I'm thinking about this incorrectly or has a solution for me that would be seriously greatly appreciated! Thank you.
path('player/<int:pk>/' means only valid int pk would match. If your pk is not int and something like a valid slug - use path('player/<slug:pk>/' instead.
docs: https://docs.djangoproject.com/en/2.1/topics/http/urls/#path-converters
And my suggestion is to use {{ player.get_absolute_url }} or {% url 'player' player.id %} instead of building url manually.
Missing leading slash means "from here", not from website root:
https://webmasters.stackexchange.com/questions/56840/what-is-the-purpose-of-leading-slash-in-html-urls
There is a mismatch between <int:pk> in your path(), which expects an integer, and your player_id, which is a string like 'aaronha01'.
You can either use the pk everywhere, and have urls like /player/17/:
path('player/<int:pk>/', views.player, name='player'),
def player(request, pk):
instance = get_object_or_404(People, pk=pk)
context = {
"instance": instance,
}
return render(request, "playerInfo.html", context)
# Use pk in template when constructing URL
<td>More on {{ player.name_first }} {{ player.name_last }}</td>
Or you can use player_id everywhere, and have urls like /player/aaronha01/.
path('player/<slug:player_id>/', views.player, name='player'),
def player(request, player_id):
instance = get_object_or_404(People, player_id=player_id)
context = {
"instance": instance,
}
return render(request, "playerInfo.html", context)
# Use player_id in template when constructing URL
<td>More on {{ player.name_first }} {{ player.name_last }}</td>
As a next improvement, you can start using the {% url %} tag so that you aren't hardcoding the URLs anymore.
<a href="{% url "polls:player" player.player_id %}">
In the above, I've assumed that you have app_name='polls' in your polls/urls.py, and that you have decided to use player_id instead of pk in your url pattern.
Related
Referring this answer
I couldn't access the kwargs in the html template by {{ view.kwargs.foo }}. Not sure why, is it because something's different with DRF so I need a different syntax to access it?
My html template ('polls/character_description_list.html'):
{% block content %}
<table>
{% for description in descriptions %}
<tr>
<td><b>{{ description.title }}</b></td>
<td>{{ description.content }}</td>
</tr>
{% endfor %}
</table>
<form action="{% url 'polls:description_detail_create_from_character' %}">
<input type="hidden" value="{{ view.kwargs.character_id }}" name="character_id"> <!-- This is where I attempt to access the kwargs but can't get it, although I can attempt to output it anywhere else for debugging -->
<input type="submit" value="New Description"/>
</form>
{% endblock %}
Therefore when submitting I expect to go to:
http://localhost:8000/myapp/description_detail_create_from_character/?character_id=1
But in reality the id is missing:
http://localhost:8000/myapp/description_detail_create_from_character/?character_id=
To check if the character_id token I am looking for is in kwargs, I did try to breakpoint (using PyCharm) in get_serializer_context:
def get_serializer_context(self):
context = super(CharacterDescriptionListView, self).get_serializer_context()
return context
Examined the context, I can find 'view' -> kwargs -> 'character_id', with the value I am expecting, so it should work.
This is my views.py:
# Already has a chain of hierarchy but basically it descends from generics.ListCreateAPIView
class CharacterDescriptionListView(DescriptionViewMixin, CustomNovelListCreateView):
template_name = 'polls/character_description_list.html'
def get_filter_object(self):
return get_object_or_404(Character, id=self.kwargs['character_id'])
def get_queryset(self):
characterObj = self.get_filter_object()
return Description.objects.filter(character=characterObj)
This is because I am using DRF's generics.ListCreateAPIView. In that case, the syntax for accessing the context I am looking for should be:
{{ serializer.context.character_id }}
Which I found after I learned that I can {% debug %} in the html template thanks to #Alasdair.
This can be confirmed by checking the source code of GenericAPIView in DRF -- which descends from View but not TemplateView so that it doesn't have get_context_data. Therefore for GenericAPIView can only access the context like {{ serializer.context.character_id }} but not {{ view.kwargs.character_id }}.
I have search results in form of table. I want to add a detail page button which can send the id of the result to another function in view.py
so i can query it from database.
{% if sr %}
{% for k,j in sr %}
<tbody>
<tr>
<td>{{ k.id }}</td>
<td>{{ k.chromosome }}</td>
<td>{{ k.gene_id }} </td>
<td> view</td>
</tr>
</tbody>
{% endfor %}
{% endif %}
I want to send this k.id to another function
def detailed(request):
return render(request,"search/Detailed.html")
so I can again perform a query from database by this id
Since you didn't specify the version of django you're using, I'm going to assume it's 2.x. The only huge difference between that and more recent versions of 1.x is the urlpatterns. If you're using 1.11, just use the required regex's you need, as described in the docs. Either way, the principle is the same.
urls.py
urlpatterns = [
....
path('<int:some_id>/', views.detail_view, name='detail_view'),
# if django 1.11... you would use ([0-9]{4}) in place of the 'int', or for whatever max amount of numbers you'd want to capture... But I will continue for django 2.x.
]
views.py
def detail_view(request, some_id):
some_object = YourModel.objects.get(id=some_id)
return render(request, 'detail_template.html', {'some_object ': some_object})
detail_template.html
<p>{{ some_object.chromosome }}</p>
<p>{{ some_object.gene_id }}</p>
<p>View details</p>
Note that the url block above has some_object.id added to it as an argument AFTER the view it goes to in quotations. This is the easiest way.
You can also acheive the same thing with a model method by using the reverse('app_name:view_name', args=[arg_1, arg_2, etc) function, and then call the method with {{ some_object.your_method }}. But those arguments would all depend on what your url patterns and functions took.
I have a rank.html which is a publicly sharing template for many other templates through {% include rank.html %} method.
This template will display the 48 hours hot news base on the click number.
Here is the view.py:
def rank(self, request):
hot_news_48h = h_mostViewed(48, News, '-pv')
return render(request, "rank.html", {
'hot_news_48h': hot_news_48h,})
h_mostViewed(48, News, '-pv') is a function,that can fetch most viewed(clicked) post within 48 hours.It works.
Here is the rank.html:
<ul>
{% for hot_view in hot_news_48h %}
<li>
<a href="{% url 'news:news_detail' hot_view.pk %}" >
<img src="{{ MEDIA_URL }}{{ hot_view.image }}" >
</a>
<a href="{% url 'news:news_detail' hot_view.pk %}">
<h6>{{ hot_view.title }}</h6>
</a>
</div>
</li>
{% endfor %}
</ul>
Here is the url.py:
path('hot_news', views.rank, name="hot_news")
The problem is,I can only get the html ,but can't receive the data.
But if I give up {% include rank.html %} method and insert the rank.html's code directly inside each template which need this function, I can get the data.
Take new_detail.html template as an example:
Here is the view.py:
def newsDetailView(request, news_pk):
news = get_object_or_404(News, id=news_pk)
all_comments = NewsComments.objects.filter(news=news)
news.comment_nums = all_comments.count()
news.save()
News.objects.filter(id=news_pk).update(pv=F('pv') + 1)
hot_news_48h = h_mostViewed(48, News, '-pv')
relative_news = News.objects.filter(tag__id__in=news.tag.all()).exclude(id=news_pk)[:6]
return render(request, "news_detail.html", {
'news': news,
'all_comments': all_comments,
'hot_news_48h': hot_news_48h,
'relative_news': relative_news
})
Here is the urls.py:
path('-<int:news_pk>', views.newsDetailView, name="news_detail"),
So above,I directly inserted rank.html's code into new_detail.html and it works I can get the data.
My question is what should I do or correct,so that I can get the data in {% include rank.html %} method. Because {% include rank.html %} is simple and flexible.I don't want to repeat the same code in several same template.
Thank you so much for your patience!
How about this:
- Create a folder "templatetags" in your application and add a file "news_tags.py" or name it what you want. Then you can define the tags you need:
from django.template import Library
from your_app.models import your_model
register = Library()
#register.inclusion_tag('your_app/your_template.html')
def hot_news(num, order):
objects = News.objects.order_by(order)[:num]
result['objects'] = objects
return result
In your templates you then do the following:
{% load news_tags %}
{% hot_news 48 '-pv' %}
Then create a template as your already did and reference it in the inclusion tag. Then it should work properly.
If you want it to work for multiple models you can have a look at this: https://docs.djangoproject.com/el/2.1/ref/applications/
The apps framework allows you to fetch models from a string input.
I finally solved the issue by Creating custom context processor.https://www.youtube.com/watch?v=QTgkGBjjVYM
I have a index.html where my form is implemented:
...
{% block searchform%} {% endblock %}
...
In my search/ folder I have two files.
search.html, which is the actual form for the index.html
{% extends 'index.html' %}
{% block searchform %}
<form method="get" action="/results/">
<table>
{{ form.as_table }}
<tr>
<td> </td>
<td>
<input type="submit" value="Search">
</td>
</tr>
</table>
{% endblock %}
and the file result.html, where I want to see my search results:
{% if query %}
<h3>Results</h3>
...
{% endif %}
urls.py:
from django.conf.urls import include, patterns, url
from dev_devices.views import *
urlpatterns = patterns('',
url(r'^$', include('haystack.urls')),
url(r'^results/$', results, name = 'results')
)
view.py
def results(request):
return render_to_response('results.html')
My problem is, that there are no results showing up. When I click on the submit Button of my form, I will redirected to the correct file (results.html), but no results are showing up. This question exists already but i found no answer, so can someone help me? Thank you !
Well, you aren't passing any results to the template. Check out your views.py:
def results(request):
return render_to_response('results.html')
Your template has this logic:
{% if query %}
<h3>Results</h3>
...
{% endif %}
query is not a variable in context. You'd have to pass the query in from the view:
def results(request):
return render_to_response('results.html', {'query': 'some query'})
But more than that, you need to get the actual results from Haystack and put those in context too. I think you are confused about what's happening here. If you want to use haystacks default views for search/results, then you shouldn't define any custom views at all. The way your results view is defined, how is Django to know this page has anything to do with Haystack at all?
If you do want to define custom views, then those views need to implement Haystack search forms, creation of SearchQuerySets, pagination, (and so on), and pass that data to the template. See the Haystack documentation on creating you own views:
http://django-haystack.readthedocs.org/en/latest/views_and_forms.html#creating-your-own-view
If you just want to see your results and this particular URL structure is not important to you, then just change your form action to point at the same page action="" and move your results display logic from results.html to search.html. That should get you the basic search results that ship with Haystacks default views and forms.
My problem is that I need to know if a user has rated a certain model instance, BlogSite. On the page, there are multiple BlogSite instances, which have a 5-star rating system. When the current user has rated a certain instance, it should be set to read-only.
I'm running into a roadblock because if I use a model function, I need to pass 2 variables - current_user and BlogSite. I haven't been able to find how to access request.user in models.py and it's looking like I shouldn't be doing that?
The other path I went down was to create a custom filter - but I found out that I can only pass in one parameter. I would rather not do this method because I feel it would be better to keep the logic in views.py
Does anyone have ideas of how I can solve this problem?
#models.py
class BlogSite(models.Model):
#fields
#get the average rating for a blogsite
def rating_avg(self):
rating_dict = BlogSiteReview.objects.filter(blog_site=self).aggregate(Avg('review_rating'))
rating_avg = rating_dict.get('review_rating__avg')
if rating_avg:
return rating_avg
else:
#no ratings
return 0
def rated(self, current_user):
#If there is a row for the blogsitereview with this blogsite for the logged in user, return True, else False
#can I access the current user? This does not work, seems like I can't get request here.
current_user = request.user
review = BlogSiteReview.objects.filter(blog_site=self, user=current_user)
if review:
return True
else:
return False
class BlogSiteReview(models.Model):
blog_site = models.ForeignKey(BlogSite)
user = models.ForeignKey(User)
#other fields
Here is the relevant part of the view:
#views.py
def search(request, type, regionValue):
#....
#ideally, the solution would be to have a field or function in the BlogSite model
blog_sites = BlogSite.objects.filter(country=region.id, active=True)
#....
In the template I would have an if statement to add a class if rated returns True
<tr>
<td>{{ blogsite.site_name }}</td>
<td><div id="rating{{ blogsite.id }}" class="rating {% if blogsite.user_rated %}jDisabled{% endif %}" data-average="{{ blogsite.rating_avg }}" data-id="{{ blogsite.id }}"></div></td>
<td>{{ blogsite.create_date }}</td>
</tr>
I'm looking for 2 things here - does using a model method to get if a user has rated seem like the correct approach? The problem I have with this so far is that I can't find how to access the current user to use in models.py. Another idea I thought of is to pass in the request.current_user from the view somehow, but the user is not associated with BlogSite, so I can't filter on that.
I ended up taking a slightly different approach which I thought of when trying to fall asleep but couldn't get Django out of my head :) In the view I created a list of blog_sites that a certain user left a review
#views.py
rated = BlogSiteReview.objects.filter(user=request.user).values_list('blog_site', flat=True)
return render(request, page, {..., 'blogsites':blog_sites, 'rated':rated})
And in the template I added a class if that blog_site FK id was in the blog_sites for loop:
#template
{% for blogsite in blogsites %}
<tr>
<td>{{ blogsite.site_name }}</td>
<td><div id="rating{{ blogsite.id }}" class="rating {% if blogsite.id in rated %}jDisabled{% endif %}" data-average="{{ blogsite.rating_avg }}" data-id="{{ blogsite.id }}"></div></td>
<td>{{ blogsite.create_date }}</td>
<td>{{ rated }}</td>
</tr>
{% empty %}
<p>There are no registered blogsites for this country.</p>
{% endfor %}
Finally I can go to sleep now!
You could pass the result of blog_site.rated(request.user) via template parameters. If you feed your template with multiple BlogSite instances (blog_sites in your view sample) and iterate over them in the template, you could group the instance and the rated result into tuples or dicts in your view like so:
({"site": block_site, "rated": block_site.rated(request.user)} for block_site in block_sites)
And access these in the template with result.site and result.rated, respectively, assuming that result is the iteration variable (because you named your view search) in the template (replacing blogsite in your template snippet).
Hence, I personally would keep the rated method as you posted it, with the current_user parameter.
EDIT: here is an example which mixes your code (question and answer) and my proposal:
#views.py
def search(request, type, regionValue):
#....
blog_sites = BlogSite.objects.filter(country=region.id, active=True)
search_results = ({"site": block_site, "rated": block_site.rated(request.user)} for block_site in block_sites)
return render(request, page, {..., 'search_results': search_results})
And in the template:
{% for result in search_results %}
{% with blogsite=result.site rated=result.rated %}
<tr>
<td>{{ result.site.site_name }}</td>
<td><div id="rating{{ blogsite.id }}" class="rating {% if rated %}jDisabled{% endif %}" data-average="{{ blogsite.rating_avg }}" data-id="{{ blogsite.id }}"></div></td>
<td>{{ blogsite.create_date }}</td>
<td>{{ rated }}</td>
</tr>
{% endwith %}
{% empty %}
<p>There are no registered blogsites for this country.</p>
{% endfor %}