I have been trying to design a page in Django that works as follows.
My "list_books.html" page lists every book object handed to it.
I have a number of functions in views.py that determine what values would be used to determine the books shown on that page (i.e. all books by an author, all books in a series, all books with the same publication year)
ex.
#with_person_email
def book_list_author(request, person):
return show_book_list(request, person.books, { 'author': person })
def show_book_list(request, blist, template_args, **kwargs):
# this is just the defaults, will be replaced by data.update below
data = { 'genre': None }
try:
# filters the list based on the keyword arguments
blist = dataview.book_list(blist, **kwargs)
except dataview.DataViewError as e:
blist = None
data['error'] = str(e)
try:
data['books'] = RequestPages(request, blist, desc=True)
except Exception as e:
if not utils.is_db_regex_exception(e):
raise
data['books'] = None
data['error'] = 'Invalid regex.'
data['genres'] = models.Genre.objects.order_by('kind', 'name')
data.update(kwargs)
data.update(template_args)
return render(request, 'book_list.html', data)
book_list.html has a for loop that goes through each book and prints information about it. However, I have a boolean on the book model called "is_archived".
I want to be able to both set "is_archived" on the book from book_list.html, and filter the books shown between archived and not. I can do both of these things currently using a form that calls the following function handing it only archived books. However, this form has no idea what the previous criteria was to sort the books, so it shows all the archived books.
def commit_list_archived(request):
return show_commit_list(request, models.Books.objects.filter(is_archived=True), { 'archived': True })
Settings the boolean is accomplished with a simple button that calls a view which changes the boolean field, and then returns to the previous page.
I want to be able to toggle between archived and non archived books. I tried using <input type="hidden" name="next" value="{{ request.path }}"> on the form to the archived posts to determine the previous criteria (author, year, genre, etc), however this doesn't seem to work.
I also considered using a checkbox that would toggle the books being shown, but I couldnt determine how to access the information of the checkbox form views.
For cleanliness sake I would like to remain on the books_list.html page, and just hand it either archived or none archived books. Again the problem is finding some way to call the right function both before and after view the archived books, to ensure I am still sorting by the same criteria.
Any help would be much appreciated.
Disregard I figured it out. I just sent a query parameter ?archived=true and have the views check for this parameter and filter the commits they send to the html template accordingly
Related
I've been trying to create user interface to filter out results from my database. Important thing is that I want the filters to be 'additive'. So if user selects one filter, page redirects and displays results. After that, user can select another filter and the results are narrowed down to both filters. This should continue for any number of filters.
This is how it looks now
#app.route('/')
def home():
kind = request.args.get('kind')
price = request.args.get('price')
category = request.args.get('category')
filters = {}
if price is not None: filters['params.price'] = {'$lt' : int(price) }
if kind is not None: filters['kind'] = kind
if category is not None: filters['category'] = category
posts = db.collection.find(filters)
return render_template('home.html', posts=posts)
and links for my hrefs using jinja2 templates look like
<li>Label</<li>
<li>Label</li>
<li>Label</li>
... many more similar links
Currently this works as override for the URL. If I click any of those links it just replaces the whole URL and uses the variable from the link.
first link: http://127.0.0.1/?kind=m
second link: http://127.0.0.1/?price=5000
third link: http://127.0.0.1/?category=p
What I'd like it to do is to append the query - If i click any of the links it remembers previous selected filters and 'adds' last clicked link. Below I show how I expect for it to work.
first link: http://127.0.0.1/?kind=m
second link: http://127.0.0.1/?kind=m?price=50000
second link: http://127.0.0.1/?kind=m?price=50000?category=p
You could pass all filter values (None initially) to the view, and add them as arguments to the url_for calls. Filters which are None will not be included in the links.
I asked this question earlier, but now I'm having trouble sorting out how to use drop downs (or even better, autofill fields) for one of the forms of a multi-form view.
The models in play are Book, BookDetails, and Genre. BookDetails is a linking table to Genre (and other similar tables) so that I can have a static list of genres etc. with unique IDs and foreign keys to BookDetails.
Right now I have this:
#views.py
def BookFormView(request):
genre = Genre.objects.all()
if request.method == "POST":
book_form = BookForm(request.POST, prefix='book')
bookdetails_form = BookDetailsForm(request.POST, prefix='bookdetails')
selected_genre = get_object_or_404(Genre, pk=request.POST.get('genre_id'))
genre.id = selected_genre
genre.save()
if book_form.is_valid() and bookdetails_form.is_valid():
book_form.save()
bookdetails_form.save()
return HttpResponseRedirect("/books/")
else:
book_form = bookForm(prefix='book')
bookdetails_form = BookDetailsForm(prefix='bookdetails)
return render(request, 'books/createbook.html',
{'book_form' : book_form,
'bookdetails_form': bookdetails_form,
'genre':genre,})
#createbook.html
<select name="genre", id="genre" form="bookform">
{% for entry in genre %}
<option value="{{ entry.id }}">
{{ entry.name }}
</option>
{% endfor %}
</select>
The form displays properly on the page, dropdown menu with options from the database included. However, when I hit submit to store the information to the database I get an error saying No Genre matches the given query The other posts on SO that regard this error don't seem to be from the same context. I think that it might be something to do with selecting a name but storing an id (for the genres), but otherwise I'm at a loss.
Normally, the way you'd do this with a form in django is not by manually pulling something out of the POST dict, but by using a ModelChoiceField:
https://docs.djangoproject.com/en/1.8/ref/forms/fields/#modelchoicefield
Was there a specific reason you didn't do that?
Also, it appears you're using the genre variable incorrectly for two different things. You initialize it with a queryset, but then try to treat it like a Genre instance later in the code. That's going to cause problems not to mention the fact that I don't think your genre.id = ... line is going to do what you expect it to.
Also, it's against style conventions to use title-casing for function names. If you're going to be doing much coding in Python, it's probably worth taking a look at the officially accepted PEP8 style guide here:
https://www.python.org/dev/peps/pep-0008/
There are a few other problems in the code but I'm not sure it's worth calling them out.
I have searched around and see that most are pointing to a search that was created by Julien Phalip: http://julienphalip.com/post/2825034077/adding-search-to-a-django-site-in-a-snap
Also the answer seems to be here: Very simple user input in django
However I am very new to Django and wanted to create a view where I actually understand what is happening so I have been going through the official Django and the Tango with Rango tutorials but I do not see a straightforward example of what I am trying to understand in regards to a simple form search. The main question I have is why is POST used in the example instead of GET? I thought POST was used to "create" data entries in mysql whereas GET is used to lookup/search for data entries? Am I missing something fundamental about using one vs the other?
I have the following simple example from my app:
models.py
class hardware(models.Model):
text = models.CharField(max_length=200, unique=TRUE)
pub_date = models.DateTimeField(default=timezone.now)
def __unicode__(self):
return self.text
class Barcode(models.Model):
hardware = models.ForeignKey(Hardware)
text = models.CharField(max_length=50)
pub_date = models.DateTimeField(default=timezone.now)
def __unicode__(self):
return self.text
forms.py
class HardwareForm(forms.modelForm):
class Meta:
model = Hardware
fields = ['text'}
views.py
def hardware_search(request):
if request.method == 'POST':
search_id = request.POST.get('textfield', None)
try:
hardwarename = Hardware.objects.get(text = search_id)
html = ("<H1>%s</H1>", hardwarename)
return HttpResponse(html)
except Hardware.DoesNotExist:
return HttpResponse("no such hardware found")
else:
return render(request, 'search.html')
search.html
<form method="POST" action="/hardware_search.html">
{% csrf_token %}
<input type="text" name="textfield">
<button type="submit">Upload text</button>
</form>
My questions are is this the most simple way to request user input to search for and generate the search results? Why is POST used? I plugged in this code and it does seem to work but i just can't understand why.
Secondly how can I display asssociated foreignkey class along with the main class 'hardware' search results? Does the ForeignKey association give a shortcut way of displaying that data as well?
thanks!
The W3 has an excellent introduction to POST vs GET here. There is a lot to be said for why someone might use POST or GET, and what their roles should be. You are probably more interested in the differences from the user's (browser's) perspective. The biggest differences between using POST and GET in a browser, is that the GET request will display the parameters in the URL. Change your form to GET to see for yourself. The user will be taken to:
/hardware_search.html?textfield=Upload%20text
As opposed to where they are taken to when the form action is POST:
/hardware_search.html
The value of the textfield field is still sent to the server, but is not visible in the URL.
There are quite a few other differences in the behavior of GET and POST in form submission. I would highly recommend reading over that introduction by the W3.
You're right, POST is not really appropriate for a search form. Using GET here would be better.
The other thing wrong is that there's no need at all for a ModelForm, or really for any kind of Django form. You're not doing any validation, you're not even using the form for output, so it would be better to leave that out altogether. That makes the view look like this:
def hardware_search(request):
query = request.GET.get('textfield', None)
if query:
try:
hardwarename = Hardware.objects.get(text = query)
html = ("<H1>%s</H1>", hardwarename)
return HttpResponse(html)
except Hardware.DoesNotExist:
return HttpResponse("no such hardware found")
else:
return render(request, 'search.html')
and you can change the form action to GET.
EDIT:
I've decided to change my question to this, in hope that a more general question will be more appealing...
so I have a model, it has a M2M field which is not translated in the server, but it is in the client side. Can I sort this field in the admin-ui, especially every time I add a new instance of the model?
This was my last attempt:
The models of interest are:
class Recipe(models.Model):
name = models.CharField(max_length=200,verbose_name=_("name"))
ingredient_list = models.ManyToManyField(IngredientType, through='Ingredient')
class IngredientType(models.Model):
name = models.CharField(max_length=25)
class Ingredient(models.Model):
ingredient_type =models.ForeignKey(IngredientType,verbose_name=_("IngredientType"))
recipe = models.ForeignKey(Recipe)
recipe_admin.py:
class IngredientInline(admin.TabularInline):
model = Ingredient
extra = 1
class RecipeAdmin(admin.ModelAdmin):
inlines = [IngredientInline]
form = RecipeForm
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
I've added a long list of IngredientType objects in my initial_data.json file.
Now, each time a person adds a new ingredient to his recipe, he selects an ingredient_type from the long drop-down list that opens up.
EDIT:
When looking at the form's html in the browser I have these lines for my drop-down list.
as you can see, underneath the field's there's a line responsible for adding another row to the inline using the showAddAnotherPopup(this) function:
(I would like to somehow hook into that function, and every time it's called, to call my sorting function)
<select id="id_ingredient_set-0-ingredient_type" name="ingredient_set-0-ingredient_type">
<option value="" selected="selected">---------</option>
<option value="42">אבוקדו</option>
<option value="80">אבטיח</option>
....
</select>
<img src="/static/admin/img/icon_addlink.gif" width="10" height="10" alt="Add another">
My problem is, that dynamically added Ingredient rows that the user adds to his recipe form, results in the drop-down ingredient_type names list to be unsorted. I am able to sort by the Hebrew alphabetical order, but i've managed to do so only for the lines already apparent when the page loads.
I've done the above by migrating the django's tabular.html file into my project file system, and adding some js that sorts it:
function sortAlpha(a,b){
return a.innerHTML.toLowerCase() > b.innerHTML.toLowerCase() ? 1 : -1;
};
and I'm calling it like so:
('#id_ingredient_set-0-ingredient_type option').sort(sortAlpha).appendTo('#id_ingredient_set-0-ingredient_type');
Inside the function that fires when the page is loaded.
but this approach obviously does not deal with all dynamically added Ingredient rows that the user adds to his recipe form, that results in the drop-down ingredient_type names list to be unsorted. (It hooks on the present id in the )
This is django's showAddAnotherPopup method which I also added to my project's file system:
function showAddAnotherPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
name = id_to_windowname(name);
href = triggeringLink.href
if (href.indexOf('?') == -1) {
href += '?_popup=1';
} else {
href += '&_popup=1';
}
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
return false;
}
I'm a django noob, so I still lack knowledge in all the AJAX/js/templatetag buzz-words...
Help will be much appreciated.
tnx
Nitzan
It's not clear in your example how you are triggering the sort function on the initial page load, but it seems you need to call the function again after each new item is added. Add an onclick, onblur, or other event trigger to the ingredient addition method that will call your sorting function again.
If you are trying to order the displayed list of items as created by the modelform, you can use the meta options order or order_with_respect_to. See https://docs.djangoproject.com/en/dev/ref/models/options/#ordering for more info.
I have a data structure that declares relationships like this (pseudocode):
class User:
...
class Rating:
rater = User
post = Post
class Post:
ratings = hasmany(Rating)
page_id = ...
I'm building a website using these models, and I'd I'm lazy, so I pass my template the current logged in User, and a bunch of Posts on the current page. My page needs to know what rating the logged in user gave to each Post, so I use SQLAlchemy's one-instance-per-session feature:
posts = session.query(Post).filter(Post.page_id==current_pageid)
ratings = session.query(Post, Rating)\
.filter(Rating.rater==user.id)\
.filter(Post.page_id==current_pageid)
for post in posts:
post.user_rating = None # default value
for post, rating in ratings:
post.user_rating = rating
Then I pass my template the posts list. Is this ugly awful practice? Can I do it some better way?
What you are doing is good enough, except that your query is lacking a WHERE clause between the Post and Rating:
# ...
.filter(Post.id==Rating.post_id)\
But you can also get the result in one query:
qry = (session.query(Post, Rating).
outerjoin(Rating, and_(Post.id==Rating.post_id, Rating.user_id==user.id)).
filter(Post.page_id==current_pageid)
)
res = qry.all() # you can return *res* already to a view, but to get to your results, do below as well:
for post, rating in res:
post.user_rating = rating
posts = [post for post, rating in res]
return posts
Note that in your case posts is not really a list, but a query, and if you iterate over it second time, you might lose the user_rating attribute. You should be cautious returning session-bound objects like query to a view. It is safer to return lists like in the same code above. To fix your code, just add .all() to the query:
posts = session.query(Post).filter(Post.page_id==current_pageid).all()
Yes, it's bad practice. And it even might (in theory) beat you at some moment, e.g. when you query from the same session without clearing it for some post SQLAlchemy will return you the same cached object with already filled rating for some user unrelated to the current context. In practice it will work find in most cases.
Why not just pass a list of (post, rating) pairs to template? Most modern template engines available for Python can iterate over the list of pairs.
BTW, you can fetch both posts and ratings with single query (rating object will be None for OUTER JOIN when it's missing):
session.query(Post, Rating).select_from(Post)\
.outerjoin(Rating, (Post.id==Rating.post_id) & (Rating.rater==…))\
.filter(Post.page_id==…)