I'm using Django haystack FacetedSearchView my views.py:
from haystack.generic_views import FacetedSearchView as BaseFacetedSearchView
class FacetedSearchView(BaseFacetedSearchView):
template_name = 'test.html'
facet_fields = ['source']
and in urls.py:
url(r'^search', FacetedSearchView.as_view(), name='haystack_search')
and in test.html I'm rendering the facets.
When I issue a request, the content of the factes context object is empty dict. but I think it should return all the I specified facets in facets_fields, and when I append q parameter to the request's querystring (with any value) it returns result but with zero document. is it neccessary to provide the q parameter? and with which value?
to solve the issue on need to override the search method of FacetedSearchForm, because the original implementation assumes a query 'q' but faceting needs only facet_fields to work.
from haystack.forms import FacetedSearchForm as BaseFacetedSearchForm
from haystack.generic_views import FacetedSearchView as BaseFacetedSearchView
class FacetedSearchForm(BaseFacetedSearchForm):
def __init__(self, *args, **kwargs):
self.selected_facets = kwargs.pop("selected_facets", [])
super(FacetedSearchForm, self).__init__(*args, **kwargs)
def search(self):
if not self.is_valid():
return self.no_query_found()
sqs = self.searchqueryset
# We need to process each facet to ensure that the field name and the
# value are quoted correctly and separately:
for facet in self.selected_facets:
if ":" not in facet:
continue
field, value = facet.split(":", 1)
if value:
sqs = sqs.narrow(u'%s:"%s"' % (field, sqs.query.clean(value)))
if self.load_all:
sqs = sqs.load_all()
return sqs
class FacetedSearchView(BaseFacetedSearchView):
template_name = 'facets.html'
facet_fields = []
form_class = FacetedSearchForm
Related
I'm completing a challenge for a job and I'm a little confused with this endpoint's response.
I have the following models:
Attribute
AttributeValue
ProductAttribute
I need to get all attributes that are linked to a given product ID. I have managed to get the values but I can't give them the correct names in the response. The relevant code is in the get_attributes_from_product function:
# src/api/viewsets/attribute.py
from django.db.models import F
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from api import errors
from api.models import Attribute, AttributeValue, ProductAttribute
from api.serializers import AttributeSerializer, AttributeValueSerializer, AttributeValueExtendedSerializer
import logging
logger = logging.getLogger(__name__)
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
"""
list: Return a list of attributes
retrieve: Return a attribute by ID.
"""
queryset = Attribute.objects.all()
serializer_class = AttributeSerializer
#action(detail=False, url_path='values/<int:attribute_id>')
def get_values_from_attribute(self, request, *args, **kwargs):
"""
Get Values Attribute from Attribute ID
"""
attribute_id = int(kwargs['attribute_id'])
# Filter queryset to find all values for attribute
response = AttributeValue.objects.filter(attribute_id=attribute_id).values(
'attribute_value_id', 'value')
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
"""
Get all Attributes with Product ID
"""
product_id = int(kwargs['product_id'])
# Filter all attributes in product
response = ProductAttribute.objects.filter(product_id=product_id).annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value')).values(
attribute_name=F('attribute_value__attribute_id__name'),
attribute_value_id=F('attribute_value_id'),
attribute_value=F('attribute_value__value')
)
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
If I change attribute_value_id=F('attribute_value_id') and attribute_value=F('attribute_value__value') to attribute_value_id1=F('attribute_value_id') and attribute_value1=F('attribute_value__value') the response is successful and all the values are correct, but obviously the key names are wrong.
It should return the following keys: attribute_name, attribute_value_id and attribute_value.
The django ORM will not overwrite existing model attributes with the names of annotated fields.
In order to use names that collide with existing model attributes, you need to use a
serializer class or just format the queryset rows before returning the response.
An example of using a serializer can be found in the django rest-framework
documentation.
Without using a queryset, you can use a list of dict objects in the response. This
is a shortcut though, and using a serializer would probably be better.
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
def render_product_attribute_row(self, row):
row["attribute_value_id"] = row.pop("tmp_attribute_value_id")
row["attribute_value"] = row.pop("tmp_attribute_value")
return row
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
product_id = int(kwargs['product_id'])
queryset = ProductAttribute.objects.filter(product_id=product_id)
queryset = queryset.annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value'),
)
queryset = queryset.values(
attribute_name=F('attribute_value__attribute_id__name'),
tmp_attribute_value_id=F('attribute_value_id'),
tmp_attribute_value=F('attribute_value__value'),
)
if queryset.exists():
status_code = 200
else:
status_code = 204
response = [self.render_product_attribute_row(row) for row in queryset]
return Response(response, status_code)
hi im trying to save a form data into db.
i provided print of requset.data for you as you see requirement have two items.
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,... in table row...
where is my mistake ... thanks
also print of request.data.get('requirement') will retun second item
this is print of request.data in sever:
<QueryDict: {'requirement': ['hello', 'bye'], 'audience': ['adasd'], 'achievement': ['asdasd'], 'section': ['410101010'], 'title': ['asdasd'], 'mini_description': ['asdad'], 'full_description': ['asdasd'], 'video_length': ['10101'], 'video_level': ['P'], 'price': [''], 'free': ['true'], 'image': [<InMemoryUploadedFile: p.gif (image/gif)>]}>
view:
class StoreCreateAPIView(generics.CreateAPIView):
parser_classes = (MultiPartParser, FormParser)
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def post(self, request, *args, **kwargs):
if request.method == 'POST':
print(request.data)
file_serial = ProductSerializer(data=request.data, context={"request": request})
if file_serial.is_valid():
file_serial.save(author_id=request.user.id)
requirement = request.data['requirement']
audience = request.data.get('audience')
achievement = request.data.get('achievement')
sections = request.data.get('section')
print(request.data['requirement'])
pid = file_serial.data.get('product_id')
for item in requirement :
req = ProductRequiredItems(
item = item,
product_id = pid
)
req.save()
First of all, overriding CreateAPIView's post method in your code makes your custom perform_create method useless, unless you explicitly call it from within your customized post method. Otherwise it will never be called.
also print of request.data.get('requirement') will retun second item
It does return the last item as per Django docs for QueryDict.__getitem__(key).
i want to save each item in database i used for loop to save each item of list but the loop will save each character of item like h-e-l,...
This is because of the above functionality of QueryDict. When you do:
requirement = request.data['requirement']
# requirement = request.__getitem__('requirement')
it will call QueryDict.__getitem__(key) method and thus return only the last item (which is string in you example).
Answer:
You can simply override CreateAPIView's create method, and let your serializer handle all the rest.
# views.py
from django.shortcuts import render
from rest_framework import generics, status
from rest_framework.response import Response
from .models import MyObj
from .serializers import MyObjSerializer
class MyObjView(generics.CreateAPIView):
serializer_class = MyObjSerializer
queryset = MyObj.objects.all()
def create(self, request, *args, **kwargs):
# The QueryDicts at request.POST and request.GET will be immutable
# when accessed in a normal request/response cycle.
# To get a mutable version you need to use QueryDict.copy().
req_data = request.data.copy()
requirements = req_data.pop('requirement')
serializers_data = []
for requirement in requirements:
req_data ['requirement'] = requirement
serializer = self.get_serializer(data=req_data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
serializers_data.append(serializer.data)
return Response(serializers_data, status=status.HTTP_201_CREATED)
# serializers.py
from rest_framework import serializers
from .models import MyObj
class MyObjSerializer(serializers.ModelSerializer):
class Meta:
model = MyObj
fields = '__all__'
Have a look at DRF CreateModelMixin. It defines create & perform_create methods that are used used in CreateAPIView upon executing POST request. I just altered them slightly to handle your specific case.
Hope it helps.
am trying to build a serializer using HyperlinkedModelSerializer, yet I have a scenario where I want to add a field which does not exist in the model but I will require the value for validating the transaction, I found the below snippet
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from rest_framework import serializers
import logging
# initiate logger
logging.getLogger(__name__)
class PostHyperlinkedModelSerializerOptions(serializers.HyperlinkedModelSerializerOptions):
"""
Options for PostHyperlinkedModelSerializer
"""
def __init__(self, meta):
super(PostHyperlinkedModelSerializerOptions, self).__init__(meta)
self.postonly_fields = getattr(meta, 'postonly_fields', ())
class PostHyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
_options_class = PostHyperlinkedModelSerializerOptions
def to_native(self, obj):
"""
Serialize objects -> primitives.
"""
ret = self._dict_class()
ret.fields = {}
for field_name, field in self.fields.items():
# Ignore all postonly_fields fron serialization
if field_name in self.opts.postonly_fields:
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
ret[key] = value
ret.fields[key] = field
return ret
def restore_object(self, attrs, instance=None):
model_attrs, post_attrs = {}, {}
for attr, value in attrs.iteritems():
if attr in self.opts.postonly_fields:
post_attrs[attr] = value
else:
model_attrs[attr] = value
obj = super(PostHyperlinkedModelSerializer,
self).restore_object(model_attrs, instance)
# Method to process ignored postonly_fields
self.process_postonly_fields(obj, post_attrs)
return obj
def process_postonly_fields(self, obj, post_attrs):
"""
Placeholder method for processing data sent in POST.
"""
pass
class PurchaseSerializer(PostHyperlinkedModelSerializer):
""" PurchaseSerializer
"""
currency = serializers.Field(source='currency_used')
class Meta:
model = DiwanyaProduct
postonly_fields = ['currency', ]
Am using the above class, PostHyperlinkedModelSerializer, but for some reason the above is causing a problem with the browsable api interface for rest framework. Field labels are disappearing, plus the new field "currency" is not showing in the form (see screenshot below for reference). Any one can help on that?
Whoever wrote the code probably didn't need browsable api (normal requests will work fine).
In order to fix the api change to_native to this:
def to_native(self, obj):
"""
Serialize objects -> primitives.
"""
ret = self._dict_class()
ret.fields = self._dict_class()
for field_name, field in self.fields.items():
if field.read_only and obj is None:
continue
field.initialize(parent=self, field_name=field_name)
key = self.get_field_key(field_name)
value = field.field_to_native(obj, field_name)
method = getattr(self, 'transform_%s' % field_name, None)
if callable(method):
value = method(obj, value)
if field_name not in self.opts.postonly_fields:
ret[key] = value
ret.fields[key] = self.augment_field(field, field_name, key, value)
return ret
I am trying to determine the best way to add a root element to all json responses using django and django-rest-framework.
I think adding a custom renderer is the best way to accomplish what I want to achieve and this is what I have come up with so far:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
#override the render method
def render(self, data, accepted_media_type=None, renderer_context=None):
#call super, as we really just want to mess with the data returned
json_str = super(CustomJSONRenderer, self).render(data, accepted_media_type, renderer_context)
root_element = 'contact'
#wrap the json string in the desired root element
ret = '{%s: %s}' % (root_element, json_str)
return ret
The tricky part now is dynamically setting the root_element based on the view that render() is being called from.
Any pointers/advice would be greatly appreciated,
Cheers
For posterity, below is the final solution. It has grown slightly from the original as it now reformats paginated results as well.
Also I should have specified before, that the reason for the JSON root element is for integration with an Ember front end solution.
serializer:
from rest_framework.serializers import ModelSerializer
from api.models import Contact
class ContactSerializer(ModelSerializer):
class Meta:
model = Contact
#define the resource we wish to use for the root element of the response
resource_name = 'contact'
fields = ('id', 'first_name', 'last_name', 'phone_number', 'company')
renderer:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
"""
Override the render method of the django rest framework JSONRenderer to allow the following:
* adding a resource_name root element to all GET requests formatted with JSON
* reformatting paginated results to the following structure {meta: {}, resource_name: [{},{}]}
NB: This solution requires a custom pagination serializer and an attribute of 'resource_name'
defined in the serializer
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
#determine the resource name for this request - default to objects if not defined
resource = getattr(renderer_context.get('view').get_serializer().Meta, 'resource_name', 'objects')
#check if the results have been paginated
if data.get('paginated_results'):
#add the resource key and copy the results
response_data['meta'] = data.get('meta')
response_data[resource] = data.get('paginated_results')
else:
response_data[resource] = data
#call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
pagination:
from rest_framework import pagination, serializers
class CustomMetaSerializer(serializers.Serializer):
next_page = pagination.NextPageField(source='*')
prev_page = pagination.PreviousPageField(source='*')
record_count = serializers.Field(source='paginator.count')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
# Takes the page object as the source
meta = CustomMetaSerializer(source='*')
results_field = 'paginated_results'
Credit to ever.wakeful for getting me 95% of the way there.
Personally, I wanted to add meta data to every api request for a certain object, regardless of whether or not it was paginated. I also wanted to simply pass in a dict object that I defined manually.
Tweaked Custom Renderer
class CustomJSONRenderer(renderers.JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
# Name the object list
object_list = 'results'
try:
meta_dict = getattr(renderer_context.get('view').get_serializer().Meta, 'meta_dict')
except:
meta_dict = dict()
try:
data.get('paginated_results')
response_data['meta'] = data['meta']
response_data[object_list] = data['results']
except:
response_data[object_list] = data
response_data['meta'] = dict()
# Add custom meta data
response_data['meta'].update(meta_dict)
# Call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
Parent Serializer and View Example
class MovieListSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
meta_dict = dict()
meta_dict['foo'] = 'bar'
class MovieViewSet(generics.ListAPIView):
queryset = Movie.objects.exclude(image__exact = "")
serializer_class = MovieListSerializer
permission_classes = (IsAdminOrReadOnly,)
renderer_classes = (CustomJSONRenderer,)
pagination_serializer_class = CustomPaginationSerializer
paginate_by = 10
does someone know how to filter in admin based on comparison on model fields - F() expressions?
Let's assume we have following model:
class Transport(models.Model):
start_area = models.ForeignKey(Area, related_name='starting_transports')
finish_area = models.ForeignKey(Area, related_name='finishing_transports')
Now, what I would like to do is to make admin filter which allows for filtering of in-area and trans-area objects, where in-area are those, whose start_area and finish_area are the same and trans-area are the others.
I have tried to accomplish this by creating custom FilterSpec but there are two problems:
FilterSpec is bound to only one field.
FilterSpec doesn't support F() expressions and exclude.
The second problem might be solved by defining custom ChangeList class, but I see no way to solve the first one.
I also tried to "emulate" the filter straight in the ModelAdmin instance by overloading queryset method and sending extra context to the changelist template where the filter itself would be hard-coded and printed by hand. Unfortunately, there seems to be problem, that Django takes out my GET parameters (used in filter link) as they are unknown to the ModelAdmin instance and instead, it puts only ?e=1 which is supposed to signal some error.
Thanks anyone in advance.
EDIT: It seems that functionality, which would allow for this is planned for next Django release, see http://code.djangoproject.com/ticket/5833. Still, does someone have a clue how to accomplish that in Django 1.2?
it's not the best way*, but it should work
class TransportForm(forms.ModelForm):
transports = Transport.objects.all()
list = []
for t in transports:
if t.start_area.pk == t.finish_area.pk:
list.append(t.pk)
select = forms.ModelChoiceField(queryset=Page.objects.filter(pk__in=list))
class Meta:
model = Transport
The solution involves adding your FilterSpec and as you said implementing your own ChangeList. As the filter name is validated, you must name your filter with a model field name. Below you will see a hack allowing to use the default filter for the same field.
You add your FilterSpec before the standard FilterSpecs.
Below is a working implementation running on Django 1.3
from django.contrib.admin.views.main import *
from django.contrib import admin
from django.db.models.fields import Field
from django.contrib.admin.filterspecs import FilterSpec
from django.db.models import F
from models import Transport, Area
from django.contrib.admin.util import get_fields_from_path
from django.utils.translation import ugettext as _
# Our filter spec
class InAreaFilterSpec(FilterSpec):
def __init__(self, f, request, params, model, model_admin, field_path=None):
super(InAreaFilterSpec, self).__init__(
f, request, params, model, model_admin, field_path=field_path)
self.lookup_val = request.GET.get('in_area', None)
def title(self):
return 'Area'
def choices(self, cl):
del self.field._in_area
yield {'selected': self.lookup_val is None,
'query_string': cl.get_query_string({}, ['in_area']),
'display': _('All')}
for pk_val, val in (('1', 'In Area'), ('0', 'Trans Area')):
yield {'selected': self.lookup_val == pk_val,
'query_string': cl.get_query_string({'in_area' : pk_val}),
'display': val}
def filter(self, params, qs):
if 'in_area' in params:
if params['in_area'] == '1':
qs = qs.filter(start_area=F('finish_area'))
else:
qs = qs.exclude(start_area=F('finish_area'))
del params['in_area']
return qs
def in_area_test(field):
# doing this so standard filters can be added with the same name
if field.name == 'start_area' and not hasattr(field, '_in_area'):
field._in_area = True
return True
return False
# we add our special filter before standard ones
FilterSpec.filter_specs.insert(0, (in_area_test, InAreaFilterSpec))
# Defining my own change list for transport
class TransportChangeList(ChangeList):
# Here we are doing our own initialization so the filters
# are initialized when we request the data
def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin):
#super(TransportChangeList, self).__init__(request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin)
self.model = model
self.opts = model._meta
self.lookup_opts = self.opts
self.root_query_set = model_admin.queryset(request)
self.list_display = list_display
self.list_display_links = list_display_links
self.list_filter = list_filter
self.date_hierarchy = date_hierarchy
self.search_fields = search_fields
self.list_select_related = list_select_related
self.list_per_page = list_per_page
self.model_admin = model_admin
# Get search parameters from the query string.
try:
self.page_num = int(request.GET.get(PAGE_VAR, 0))
except ValueError:
self.page_num = 0
self.show_all = ALL_VAR in request.GET
self.is_popup = IS_POPUP_VAR in request.GET
self.to_field = request.GET.get(TO_FIELD_VAR)
self.params = dict(request.GET.items())
if PAGE_VAR in self.params:
del self.params[PAGE_VAR]
if TO_FIELD_VAR in self.params:
del self.params[TO_FIELD_VAR]
if ERROR_FLAG in self.params:
del self.params[ERROR_FLAG]
if self.is_popup:
self.list_editable = ()
else:
self.list_editable = list_editable
self.order_field, self.order_type = self.get_ordering()
self.query = request.GET.get(SEARCH_VAR, '')
self.filter_specs, self.has_filters = self.get_filters(request)
self.query_set = self.get_query_set()
self.get_results(request)
self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name))
self.pk_attname = self.lookup_opts.pk.attname
# To be able to do our own filter,
# we need to override this
def get_query_set(self):
qs = self.root_query_set
params = self.params.copy()
# now we pass the parameters and the query set
# to each filter spec that may change it
# The filter MUST delete a parameter that it uses
if self.has_filters:
for filter_spec in self.filter_specs:
if hasattr(filter_spec, 'filter'):
qs = filter_spec.filter(params, qs)
# Now we call the parent get_query_set()
# method to apply subsequent filters
sav_qs = self.root_query_set
sav_params = self.params
self.root_query_set = qs
self.params = params
qs = super(TransportChangeList, self).get_query_set()
self.root_query_set = sav_qs
self.params = sav_params
return qs
class TransportAdmin(admin.ModelAdmin):
list_filter = ('start_area','start_area')
def get_changelist(self, request, **kwargs):
"""
Overriden from ModelAdmin
"""
return TransportChangeList
admin.site.register(Transport, TransportAdmin)
admin.site.register(Area)
Unfortunately, FilterSpecs are very limited currently in Django. Simply, they weren't created with customization in mind.
Thankfully, though, many have been working on a patch to FilterSpec for a long time. It missed the 1.3 milestone, but it looks like it's now finally in trunk, and should hit with the next release.
#5833 (Custom FilterSpecs)
If you want to run your project on trunk, you can take advantage of it now, or you might be able to patch your current installation. Otherwise, you'll have to wait, but at least it's coming soon.