Django 1.11: export queryset to csv using classes - python

I am having trouble exporting the results of a Django query to csv using a subclass of my query class as suggested here. https://stackoverflow.com/a/29678525/3973597
I end up with a Page not found (404) error. Here is the relevant code...
views.py
class QueryResultsView(ListView):
template_name = 'query/query_results.html'
model = StDetail
context_object_name = "object_list"
def get_queryset(self):
form_input = self.request.GET
filters = {"person_name": form_input.get('name'),
"city": form_input.get('city'),
}
# delete keys that are None
filters = {k: v for k, v in filters.items() if v is not ''}
self.detail_data = get_list_or_404(self.model, **filters)
return(self.detail_data)
def get_context_data(self, **kwargs):
context = super(QueryResultsView, self).get_context_data(**kwargs)
context['query_results'] = self.get_queryset()
return(context)
class QueryResultsCsvView(QueryResultsView):
# Subclass of above view, to produce a csv file
template_name = 'query/QueryResults.csv'
content_type = 'text/csv'
urls.py
app_name = QueryConfig.name
urlpatterns = [
...
url(r'^query',
QueryFormView.as_view(),
name='person-query'),
url(r'^results',
QueryResultsView.as_view(),
name='query-results'),
url(r'^results/csv/$',
QueryResultsCsvView.as_view(),
name='query-results-csv'),
]
query_results.html
...
Download Results
...
QueryResults.csv
Date, City, Name, Sex
{% for object in object_list %}
{{object.date}},{{object.city}},{{object.name}},{{object.sex}}
{% endfor %}
The query works without any problems. However, when I click on the Download Results link I get a Page not found (404) error. Can somebody tell me what I'm missing?

Your first problem is that r'^results'matches results/csv/ as well as results because it doesn't use a $ to match the end of the URL.
Try something like the following (I've added trailing slashes to match the usual Django style).
urlpatterns = [
url(r'^query/$',
QueryFormView.as_view(),
name='person-query'),
url(r'^results/$',
QueryResultsView.as_view(),
name='query-results'),
url(r'^results/csv/$',
QueryResultsCsvView.as_view(),
name='query-results-csv'),
]
Once you've done that, you're next problem is that you are simply linking to the query-results-csv results view, but you are not passing any form data to it. In the template for the query-results view, you could add the same querystring to the link by changing it to:
Download Results
Finally, note that form_input.get(key) returns None if the key does not exist, therefore your filter should use if v instead of if v is not '' (if you really only wanted to exclude values with the empty string, you should have used if v != '').
filters = {k: v for k, v in filters.items() if v}

Related

Getting two database models to show on one html page

Currently working on a web page and I want to show two different tables of information one is for individual foods and the other is for recipes, however I can only get the first class to pull up any information I've tested to see if the first class can pull up the recipe database and it, in fact, currently out of ideas on what to try next.
class SearchFoodResultsView(ListView):
model = Food
template_name = 'nutrihacker/search.html'
context_object_name = 'food_list'
# overrides ListView get_queryset to find names containing search term and pass them to template
def get_queryset(self):
query = self.request.GET.get('term')
if (query == None):
return Food.objects.all()
else: # If there are any foods containing the query, they will be in the resulting object_list which is used by search.html in a for loop
food_list = Food.objects.filter(
Q(name__icontains = query)
)
return food_list
class SearchRecipeResultsView(ListView):
model = RecipePreset
template_name = 'nutrihacker/search.html'
context_object_name = 'recipe_list'
# overrides ListView get_queryset to find names containing search term and pass them to template
def get_queryset(self):
query = self.request.GET.get('term')
if (query == None):
return RecipePreset.objects.all()
else: # If there are any recipes containing the query, they will be in the resulting object_list which is used by search.html in a for loop
recipe_list = RecipePreset.objects.filter(
Q(name__icontains = query)
)
return recipe_list

Automatically get name of the column with for loop in django template

I would like to automatically retrieve the name of the column with a for loop.
If I know the name of the columns I can write the template in this way:
<ul>
{% for co in team %}
<li>{{ co.name }}</li>
<li>{{ co.team}}</li>
<li>{{ co.sport}}</li>
{% endfor %}
</ul>
But in my present case, I do not know the names of the columns of my table. How can I display them?
Thank you
You can obtain the fields of a model with:
fields = Model._meta.get_fields()
This is a tuple of Field [GitHub] objects. These Field objects have a name attribute, so we can obtain the field names with:
from operator import attrgetter
field_names = map(attrgetter('name'), Model._meta.get_fields())
In case we do not know what the model is, we can obtain a reference to the model of a model instance with type(..):
fields = type(some_instance)._meta.get_fields()
All this logic however does not really belong in the Django template, but in the view (by design, Django templates are a bit restrictive to prevent writing business logic in the template). We can thus pass a list of field names to the template:
def some_view(request):
teams = Team.objects.all()
field_names = [f.name for f in Team._meta.get_fields()]
return render(request, 'some_template.html',
{'teams': teams, 'field_names': field_names}
)
If you however want to print the values of these teams, then this will still not work, since we can not easily get an arbitrary attribute in a template. Then we can again shift some processing to the view:
from operator import attregetter
def some_view(request):
teams = Team.objects.all()
field_names = [f.name for f in Team._meta.get_fields()]
atgetters = [attrgetter(fn) for fn in field_names]
rows = [[ag(team) for ag in atgetters] for team in teams]
return render(request, 'some_template.html',
{'teams': teams, 'field_names': field_names, 'data': data}
)
So here data is a list containing lists, such that data[i][j] contains the value for a field with name field_name[j] for teams[i].

Django filter multiple value in the same column

I have set up the column where one of the table is called Establishment_Type
Now, I am trying to filter according to Establishment_Type.
Here is my view.py code
class ShopDetailAPIView(ListAPIView):
serializer_class = ShopDetailSerializer
def get_queryset(self):
queryset = Shop.objects.all()
type = self.request.query_params.get('type', None)
type2 = self.request.query_params.get('type2', None)
if type is not None and type2 is None:
queryset = queryset.filter(Establishment_Type = type)
elif type is not None and type2 is not None:
queryset = queryset.filter(Q(Establishment_Type = type) | Q(Establishment_Type = type2))
return queryset
In the url, I query by typing:
http://127.0.0.1:8000/shop/search/?type=Restaurant&type2=Petrol%20Station
Which only filter Establishment_Type = Restaurant but not include Establishment_Type = Petrol Station
Here is my urls.py within my app called shop:
urlpatterns = [
url(r'^$', ShopListAPIView.as_view(), name = 'list' ),
#####
url(r'^create/$', ShopCreateAPIView.as_view(), name = 'create' ),
url(r'^search/$', ShopDetailAPIView.as_view(), name = 'detail'),
]
Was my url for filtering 2 Establishment type wrong?
Do I need to change something in my code in order to filter 2 values in column Establishment_Type?
Thanks #Thyrst' for advising me to use Establishment_Type__in=types
I have modify my code this way for my filter to work
class ShopDetailAPIView(ListAPIView):
serializer_class = ShopDetailSerializer
def get_queryset(self):
queryset = Shop.objects.all()
type = self.request.query_params.get('type', None)
type = type.split(',')
if type is not None:
queryset = queryset.filter(Establishment_Type__in = type)
return queryset
so type is now list, therefore when entered the url:
http://127.0.0.1:8000/shop/search/?type=Restaurant,Petrol%20Station
it filters according to Restaurant and Petrol Station.
it also works when entering just 1 value or more.
This is good for now but I feel like there might be a better way to implement this.
There is an even simpler way to achieve this using the django-filter package. Deep within the django-filter documentation, it mentions that you can use "a dictionary of field names mapped to a list of lookups".
Your code would be updated like so:
# views.py
from django_filters.rest_framework import DjangoFilterBackend
class ShopDetailAPIView(ListAPIView):
queryset = Shop.objects.all()
serializer_class = ShopDetailSerializer
filter_backends = [DjangoFilterBackend]
filter_fields = {
'type': ["in", "exact"]
}
Now in the URL you would add __in to the filter before supplying your list of parameters and it would work as you expect:
http://127.0.0.1:8000/shop/search/?type__in=Restaurant,Petrol%20Station
The django-filter documentation on what lookup filters are available is quite poor, but the in lookup filter is mentioned in the Django documentation itself.

How to make foreign key accept field value instead of its id in django?

I have created a checkbox for content filtering of products based on category.So when the user clicks on any checkbox only the books with that category should be shown.In the view I am passing the value of checkbox field(category name) obtained from the template but upon filtering, the foreign key is expecting pk(id) instead of field value.I am getting error like this,invalid literal for int() with base 10: '<category name>'.So is it possible to make foreign key accept value instead of id?
Models.py,
class Add_cat(models.Model):
category = models.CharField("Name",max_length=25,unique=True)
def __unicode__(self):
return u'{0}'.format(self.category)
class Add_prod(models.Model):
book = models.CharField("Book Name",max_length=40)
author = models.CharField("Author",max_length=30)
price = models.PositiveIntegerField("Price")
image = models.ImageField(upload_to='images',null=True)
cat = models.ForeignKey(Add_cat,on_delete=models.PROTECT)
Template file,
{% for i in products %}
<input type="checkbox" name="cat_name" value="{{i.cat}}">{{i.cat}}<br>
{% endfor %}
Views.py,
def welcome_user(request):
if 'cat_name' in request.GET:
filter_category = request.GET.get('cat_name')
my_products = Add_prod.objects.filter(cat__in = filter_category)
context = { "products":my_products}
else:
my_products = Add_prod.objects.all()
context = { "products":my_products}
return render(request,"welcome-user.html",context)
You can check in the category field itself:
my_products = Add_prod.objects.filter(cat__category__in=filter_category)
Have a look at the documentation on how this works.
Above, is only applicable if filter_category is a list. If it is a string you can filter like following:
my_products = Add_prod.objects.filter(cat__category=filter_category)
There are two things wrong with your code
You need to look up the field rather than the foreign key
By using __in you are looking the category is equal to any one of the characters in the filter_category.
Hence to fix, use the field lookup and remove the __in
Add_prod.objects.filter(cat__category=filter_category)
You can try this,it will help you:
Add_prod.objects.filter(cat__category = filter_category)

How to filter for multiple ids from a query param on a GET request with django rest framework?

I'm trying to make a web app API. I want to make an API request where multiple ids can be submitted.
The django rest framework tutorial shows how to get all records from a model. For example http://127.0.0.1:8000/snippets/ will return all snippet records. The tutorial also shows how to retrieve a single item from a model. http://127.0.0.1:8000/snippets/2/ will return only snippet record with pk=2.
I'd like to be able to request multiple records, but not all records.
How could I change this code so I could request multiple snippets?
snippets/urls.py
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
snippets/views.py
def snippet_detail(request, *pk):
try:
snippet = Snippet.objects.filter(pk__in=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JSONResponse(serializer.data)
Based in your comment, you could send the ids via url:
127.0.0.1:8000/snippets/?ids=2,3,4
and in your view
...
ids = request.GET.get('ids') # u'2,3,4' <- this is unicode
ids = ids.split(',') # [u'2',u'3',u'4'] <- this is a list of unicodes with ids values
Then you can query to Snippet model:
Snippet.objects.filter(pk__in=ids)
This could give you some problems if there's spaces between ids in url:
127.0.0.1:8000/snippets/?ids=2, 3 , 4
You could need process every value before perform a query
I found this to work, following the Django REST Framework main tutorial and then documentation on Filtering against query parameters, adapting slightly. This allows a single url to return data from two GET requests: one returning objects whose ids match those given as a parameter, the other returning all objects, when no parameters are provided.
http://127.0.0.1:8000/snippets/ returns all snippets
http://127.0.0.1:8000/snippets/?ids=2,3,7 returns only snippets with id 2, 3 and 7
snippets/urls.py
from django.conf.urls import url
from snippets import views
urlpatterns = [
.... (code for other urls here)
url(r'^snippets/$', views.SnippetList.as_view(), name='snippet-list'),
....
]
snippets/views.py
....
from snippet.serializers import SnippetSerializer
....
class SnippetList(generics.ListCreateAPIView):
serializer_class = SnippetSerializer
def get_queryset(self):
# Get URL parameter as a string, if exists
ids = self.request.query_params.get('ids', None)
# Get snippets for ids if they exist
if ids is not None:
# Convert parameter string to list of integers
ids = [ int(x) for x in ids.split(',') ]
# Get objects for all parameter ids
queryset = Product.objects.filter(pk__in=ids)
else:
# Else no parameters, return all objects
queryset = Product.objects.all()
return queryset
snippets/serializers.py
....
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('url', 'id', 'title', 'code', 'linenos', 'language', 'style')
You can use django-filter and set it up as a filter and avoid over riding the get_queryset method on the viewset
Given this request
/api/snippets/?ids=1,2,3,4
Then write a django filter set and method
import django_filters
def filter_by_ids(queryset, name, value):
values = value.split(',')
return queryset.filter(id__in=values)
class SnippetFilterSet(django_filters.FilterSet):
ids = django_filters.CharFilter(method=filter_by_ids)
class Meta:
model = Snippet
fields = ['ids']
And then in your ModelViewSet
from rest_framework.viewsets import ModelViewSet
from app.snippets.models import Snippet
from app.snippets.filters import SnippetFilterSet # path to filterset
class SnippetView(ModelViewSet):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
filterset_class = SnippetFilterSet
A possible way is to send the lis of pk(s) as GET request data, somthing like this:
GET request to "/snippets"
Request body: {"list_of_pk": [1,2,3...]}
And then:
snippets/urls.py
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
snippets/views.py
def snippet_list(request):
if request.method == 'GET':
pk_list = request.GET.get('list_of_pk')
if pk_list:
snippets = Snippet.objects.filter(pk__in=pk_list)
else:
snippets = Snippet.objects.all()
#the serialization...
Here's what I ended up going with:
No changes to
snippet/urls.py
from django.conf.urls import url
from snippets import views
urlpatterns = [
url(r'^snippets/$', views.snippet_list),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]
http://127.0.0.1:8000/snippets/?ids=2,3,4 is received by
snippet/views.py
from rest_framework.decorators import api_view
#api_view(['GET', 'POST'])
def snippet_list(request):
if request.method == 'GET':
ids = request.query_params.get('ids') # u'2,3,4' <- this is unicode
ids = ids.split(',')
snippets = Snippet.objects.filter(pk__in=ids)
serializer = SnippetSerializer(snippet, many=True)
return JSONResponse(serializer.data)
While there is no definitive standard, most web frameworks allow multiple values to be associated with a single field (e.g. field1=value1&field1=value2&field2=value3).
Django also supports this.
To get list of values you can use: ids = request.GET.getlist('ids').
More examples from MultiValueDict docs:
>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
>>> d['name']
'Simon'
>>> d.getlist('name')
['Adrian', 'Simon']
>>> d.getlist('doesnotexist')
[]
>>> d.getlist('doesnotexist', ['Adrian', 'Simon'])
['Adrian', 'Simon']
>>> d.get('lastname', 'nonexistent')
'nonexistent'
>>> d.setlist('lastname', ['Holovaty', 'Willison'])
The django-filter documentation give a good example how to do it: https://django-filter.readthedocs.io/en/stable/ref/filters.html?highlight=ids#baseinfilter
For your case:
class SnippetFilterSet(BaseInFilter, NumberFilter):
pass
class F(FilterSet):
ids = NumberInFilter(field_name='id', lookup_expr='in')
class Meta:
model = Snippet

Categories

Resources