I have just installed the django-ratings app and I am having some trouble getting it working.
I have successfully added a rating field to my model, however, in the documentation, it says you can get a list of records by rating by using the following code:
# In this example, ``rating`` is the attribute name for your ``RatingField``
qs = qs.extra(select={
'rating': '((100/%s*rating_score/(rating_votes+%s))+100)/2' % (MyModel.rating.range, MyModel.rating.weight)
})
qs = qs.order_by('-rating')
I have added the following to my class based generic view:
def get_queryset(self):
return Resource.objects.filter(user=self.request.user).extra(select={
'rating': '((100/%s*rating_score/(rating_votes+%s))+100)/2' % (Resource.rating.range, Resource.rating.weight)
})
However, this gives me the following error:
'RatingField' object has no attribute 'range'
Can anyone see what I might have done wrong?
Any advice appreciated.
Thanks.
Related
Using:
Django 3.x [ Django-Filters 2.2.0, graphene-django 2.8.0, graphql-relay 2.0.1 ]
Vue 2.x [ Vue-Apollo ]
After applying some filters (iContains etc.) on my graphQL search i tried to change or manipulate the connection_args like firstor after. I can fetch a Dictionary on my resolver like {'first': 2, 'name__icontains': 'eagle'} with values i put in the IDE. As you can see (Example 1 /def resolve_all_birds2) i use that already for a logic. But i do not understand where do manipulate the GraphQLArgument states of the before. after first. last function which comes with relay?
Example 1
class ExtendedConnection(Connection):
class Meta:
abstract = True
total_count = Int()
edge_count = Int()
def resolve_total_count(root, info, **kwargs):
return root.length
def resolve_edge_count(root, info, **kwargs):
return len(root.edges)
class Birds2Node(DjangoObjectType):
class Meta:
model = Birds
filter_fields = {
'id': ['exact', 'icontains'],
'name': ['exact', 'icontains', 'istartswith', 'iendswith'],
}
interfaces = (relay.Node, )
connection_class = ExtendedConnection
# --- CUSTOM FIELDS -->
# pkey = _db primary key
pKey = Int()
def resolve_pKey(parent, info):
return parent.pk
# qRank = Item Rank in Edge Array
qRank = Int()
def resolve_qRank(parent, info, **kwargs):
return info.path[2]
class Birds2Query(ObjectType):
birds2 = relay.Node.Field(Birds2Node)
all_birds2 = DjangoFilterConnectionField(Birds2Node)
def resolve_all_birds2(self, info, **kwargs):
if 'name__icontains' in kwargs:
nameIcon = kwargs['name__icontains']
nameIconBool = bool(nameIcon.strip()) # if blanks turns False
if nameIconBool == False: # has blanks
return Birds.objects.filter(name=None)
pass
if 'name__istartswith' in kwargs:
nameIsta = kwargs['name__istartswith']
nameIstaBool = bool(nameIsta.strip()) # if blanks turns False
if nameIstaBool == False: # has blanks
return Birds.objects.filter(name=None)
pass
return
For example, in my IDE i declare allBirds2(first: 2, name_Icontains: "a")... i can fetch these values with my resolver as a Dictionary via **kwargs`` or via args def resolve_all_birds2(self, info, first, name_icontains): so far so good, i can manipulate my ModelQuery and it returned only 2 per Edge.
But Imagine i want to change first: 2 to first: 10 in my BackEnd? Can i update the Dictionary? The Documentation means yes, but it seems strict related to the ObjectTypes (Fields) you resolve.
For Example i tried this...
Example 2
def resolve_all_birds2(self, info, **kwargs):
<...>
return {'first': '20', 'name__icontains': 'd' }
Output IDE: "message": "'dict' object has no attribute 'model'"
Example 3
def resolve_all_birds2(self, info, first, **kwargs):
<...>
return f'20, {first}!'
Output IDE: "message": "name 'first' is not defined",
Question
Unfortunately i found only parameter manipulation on the modelquery in the graphene-python docs.
So my Question is how can i manipulate - in my backend - the Values of the Fields before. after first. last, that relay offers and that are already useable in my IDE. Do i have to declare them extra in my DjangoObjectType or create a custom Node to manipulate and change the values after a user sends a request?
Adding a middleware would probably allow changing the input values after the request is made and before running the query. Graphene has an example at: https://docs.graphene-python.org/en/latest/execution/middleware/
However, it's not clear (to me) from the documentation which of the mentioned parameters would contain the first field you want to manipulate.
The middleware approach does not seem to be highly recommended, though, because this is an undesirable side effect: https://github.com/graphql-python/graphene/issues/1285
I have a Class Based View to list animals from a specific herd. There are multiple herds, so the user can either see all animals from ONE herd, or all animals from ALL herds.
How do I have an optional URL parameter and handle it in the CBV?
urls:
url(r'list/(?P<hpk>[0-9]+)/$', AnimalList.as_view(), name = 'animal_list'),
url(r'list/$', AnimalList.as_view(), name = 'animal_list'),
My view:
class AnimalList(ListView):
model = Animal
def get_queryset(self):
if self.kwargs is None:
return Animal.objects.all()
return Animal.objects.filter(herd = self.kwargs['hpk']) # <--- line 19 that returns an error
Going to a URL of like /animals/list/3/ works fine, while /animals/list/ fails with an error. Here's that error:
KeyError at /animals/list/
'hpk'
Request Method: GET
Request URL: http://localhost:8000/animals/list/
Django Version: 1.8.2
Exception Type: KeyError
Exception Value:
'hpk'
Exception Location: /var/www/registry/animals/views.py in get_queryset, line 19
I get that the self.kwargs is a dictionary, and when I print() it inside the view, it'll show it's empty. But I can't figure out how to capture that scenario. I feel like this is a simple, stupid error I'm missing.
To anyone who may stumble on this and need an answer, here is my working code after figuring it out:
class AnimalList(ListView):
model = Animal
def get_queryset(self):
if 'hpk' in self.kwargs:
return Animal.objects.filter(herd = self.kwargs['hpk'])
return Animal.objects.all()
Essentially we test to see if the URL parameter hpk is present in the list of self.kwargs. If it is, we filter the queryset. Otherwise, we return all animals.
Hope this helps someone :)
I would implement this using GET parameters instead of separate URLs. With this approach, there is only one URL /list/ that is filtered by parameters, for example /list/?hpk=1.
This is more flexible as you can eventually add more queries /list/?hpk=1&origin=europe
#url(r'list/$', AnimalList.as_view(), name = 'animal_list'),
class AnimalList(ListView):
model = Animal
def get_queryset(self):
queryset = Animal.objects.all()
hpk = self.request.GET.get("hpk"):
if hpk:
try:
queryset = queryset.filter(herd=hpk)
except:
# Display error message
return queryset
Full Disclosure: Cross posted to Tastypie Google Group
I have a situation where I have limited control over what is being sent to my api. Essentially there are two webservices that I need to be able to accept POST data from. Both use plain POST actions with urlencoded data (basic form submission essentially).
Thinking about it in "curl" terms it's like:
curl --data "id=1&foo=2" http://path/to/api
My problem is that I can't update records using POST. So I need to adjust the model resource (I believe) such that if an ID is specified, the POST acts as a PUT instead of a POST.
api.py
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencoded']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencoded': 'application/x-www-form-urlencoded',
}
# cheating
def to_urlencoded(self,content):
pass
# this comes from an old patch on github, it was never implemented
def from_urlencoded(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
resource_name = 'foo'
authorization = Authorization() # only temporary, I know.
serializer = urlencodeSerializer()
urls.py
foo_resource = FooResource
...
url(r'^api/',include(foo_resource.urls)),
)
In #tastypie on Freenode, Ghost[], suggested that I overwrite post_list() by creating a function in the model resource like so, however, I have not been successful in using this as yet.
def post_list(self, request, **kwargs):
if request.POST.get('id'):
return self.put_detail(request,**kwargs)
else:
return super(YourResource, self).post_list(request,**kwargs)
Unfortunately this method isn't working for me. I'm hoping the larger community could provide some guidance or a solution for this problem.
Note: I cannot overwrite the headers that come from the client (as per: http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
I had a similar problem on user creation where I wasn't able to check if the record already existed. I ended up creating a custom validation method which validated if the user didn't exist in which case post would work fine. If the user did exist I updated the record from the validation method. The api still returns a 400 response but the record is updated. It feels a bit hacky but...
from tastypie.validation import Validation
class MyValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
#if this dict is empty validation passes.
my_foo = foo.objects.filter(id=1)
if not len(my_foo) == 0: #if object exists
foo[0].foo = 'bar' #so existing object updated
errors['status'] = 'object updated' #this will be returned in the api response
return errors
#so errors is empty if object does not exist and validation passes. Otherwise object
#updated and response notifies you of this
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
validation = MyValidation()
With Cathal's recommendation I was able to utilize a validation function to update the records I needed. While this does not return a valid code... it works.
from tastypie.validation import Validation
import string # wrapping in int() doesn't work
class Validator(Validation):
def __init__(self,**kwargs):
pass
def is_valid(self,bundle,request=None):
if string.atoi(bundle.data['id']) in Foo.objects.values_list('id',flat=True):
# ... update code here
else:
return {}
Make sure you specify the validation = Validator() in the ModelResource meta.
I'm trying to setup Django Haystack to search based on some pretty urls. Here is my urlpatterns.
urlpatterns += patterns('',
url(r'^search/$', SearchView(),
name='search_all',
),
url(r'^search/(?P<category>\w+)/$', CategorySearchView(
form_class=SearchForm,
),
name='search_category',
),
)
My custom SearchView class looks like this:
class CategorySearchView(SearchView):
def __name__(self):
return "CategorySearchView"
def __call__(self, request, category):
self.category = category
return super(CategorySearchView, self).__call__(request)
def build_form(self, form_kwargs=None):
data = None
kwargs = {
'load_all': self.load_all,
}
if form_kwargs:
kwargs.update(form_kwargs)
if len(self.request.GET):
data = self.request.GET
kwargs['searchqueryset'] = SearchQuerySet().models(self.category)
return self.form_class(data, **kwargs)
I keep getting this error running the Django dev web server if I try and visit /search/Vendor/q=Microsoft
UserWarning: The model u'Vendor' is not registered for search.
warnings.warn('The model %r is not registered for search.' % model)
And this on my page
The model being added to the query must derive from Model.
If I visit /search/q=Microsoft, it works fine. Is there another way to accomplish this?
Thanks for any pointers
-Jay
There are a couple of things going on here. In your __call__ method you're assigning a category based on a string in the URL. In this error:
UserWarning: The model u'Vendor' is not registered for search
Note the unicode string. If you got an error like The model <class 'mymodel.Model'> is not registered for search then you'd know that you haven't properly created an index for that model. However this is a string, not a model! The models method on the SearchQuerySet class requires a class instance, not a string.
The first thing you could do is use that string to look up a model by content type. This is probably not a good idea! Even if you don't have models indexed which you'd like to keep away from prying eyes, you could at least generate some unnecessary errors.
Better to use a lookup in your view to route the query to the correct model index, using conditionals or perhaps a dictionary. In your __call__ method:
self.category = category.lower()
And if you have several models:
my_querysets = {
'model1': SearchQuerySet().models(Model1),
'model2': SearchQuerySet().models(Model2),
'model3': SearchQuerySet().models(Model3),
}
# Default queryset then searches everything
kwargs['searchqueryset'] = my_querysets.get(self.category, SearchQuerySet())
Newbie question:
I need to accept a parameter in a form from a method in views.py but it gave me troubles. In the view I created a method with following snippet:
def scan_page(request):
myClient = request.user.get_profile().client
form = WirelessScanForm(client = myClient) # pass parameter to the form
and in the forms.py I defined the following form:
class WirelessScanForm(forms.ModelForm):
time = forms.DateTimeField(label="Schedule Time", widget=AdminSplitDateTime())
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
prob = forms.ChoiceField(label="Sniffer", choices=[ x.sniffer.plug_ip for x in Sniffer.objects.filter(client = myClient) ])
But django keeps giving me error saying: TemplateSyntaxError: Caught NameError while rendering: name 'myClient' is not defined(This error happens in the query)
I'm afraid it would be something stupid missing here, but I cannot really figure out why. Please help, thanks.
Assuming I've corrected your formatting properly, you have an indentation issue: prob is outside __init__, so doesn't have access to the local myClient variable.
However if you bring it inside the method, it still won't work, as there are two other issues: first, simply assigning a field to a variable won't set it on the form; and second, the choices attribute needs a list of 2-tuples, not just a flat list. What you need is this:
def __init__(self,*args,**kwargs):
myClient = kwargs.pop("client") # client is the parameter passed from views.py
super(WirelessScanForm, self).__init__(*args,**kwargs)
self.fields['prob'] = forms.ChoiceField(label="Sniffer", choices=[(x.plug_ip, x.MY_DESCRIPTIVE_FIELD) for x in Sniffer.objects.filter(client = myClient)])
Obviously replace MY_DESCRIPTIVE_FIELD with the actual field you want displayed in the choices.