How do I expose Wagtail search in a Django Graphene query? - python

Given the general structure:
class Article(Page):
body = RichTextField(...)
search_fields = Page.search_fields + [index.SearchField('body')]
class ArticleFilter(FilterSet):
search = SearchFilter()
class Meta:
model = Article
fields = ['slug']
class Query(ObjectType):
articles = DjangoFilterConnectionField(ArticleNode, filterset_class=ArticleFilter)
I thought to create a "SearchFilter" to expose the wagtail search functionality, since I ultimately want to perform full text search via graphql like so:
query {
articles (search: "some text in a page") {
edges {
nodes {
slug
}
}
}
}
"search" is not a field on the Django model which is why I created a custom field in the Django FilterSet. My thought was to do something like:
class SearchFilter(CharFilter):
def filter(self, qs, value):
search_results = [r.pk for r in qs.search(value)]
return self.get_method(qs)(pk__in=search_results)
however, I'm curious if there's a better pattern that's more efficient. At the bare minimum I'd want to ensure the SearchFilter is added last (so the query searched is filtered first).
Should the "search" be moved outside of the FilterSet and into the Query/Node/custom connection, and if so, how can I add an additional field to "articles" to see it as the final step in resolving articles (i.e. tack it on to the end of the filter queryset)? If this does belong in a separate Connection, is it possible to combine that connection with the django filter connection?
I would think this pattern of accessing Wagtail search via graphene already exists, however I've had no luck on finding this in the documentation.

Related

Djanjo with Rest Framework and Postgres search

I need to make search for looking some data from database and this search field should support some logical operations. Like AND, OR, NOT and so on.
So I found that Postgres db has something that I need. Ad Django documentation saying to us we need to use SearchQuery with parameter search_type='websearch'
And we have something like
SearchQuery("'tomato' ('red' OR 'green')", search_type='websearch') # websearch operators
When I'm tried to implement to my project I did something like that
# views.py
class DataSearch(generics.ListAPIView):
serializer_class = DataSerializer
def get_queryset(self):
queryset = Data.objects.all()
query = self.request.query_params.get('query')
if query is not None:
queryset = queryset.filter(
search_vector = SearchQuery(
query,
search_type='websearch'
)
)
return queryset
But when I'm trying to run it I get
Cannot resolve keyword 'search_vector' into field. Choices are: data, foo, foo_id
So what I should do to make it work and to make API endpoint for this type of search?

Update multiple fields on Google NDB entity

Working with Google App Engine for Python, I am trying to create and then update an ndb entity. To update a single property, you can just access the property using a dot, e.g.
post.body = body
But I would like to know if there is a simple way to update multiple fields within an ndb entity. The following code:
class Create(Handler):
def post(self):
## code to get params
post = Post(author = author,
title = title,
body = body)
post.put()
class Update(Handler):
def post(self, post_id):
post = post.get_by_id(int(post_id))
fields = ['author', 'title', 'body']
data = get_params(self.request, fields)
for field in fields:
post[field] = data[field]
post.put()
The "Create" handler works fine, but the "Update" handler results in:
TypeError: 'Post' object does not support item assignment
So it seems I would need to access the properties using a dot, but that is not going to work when I have a list of properties I want to access.
Can someone provide an alternative way to update multiple properties of an NDB entity after it has been created?
You should use setattr.
for field in fields:
setattr(post, field, data[field])
(Note that GAE objects do actually provide a hidden way of updating them via a dict, but you should use the public interface.)
You can use the populate method:
post.populate(**data)

Sort Django Rest Framework JSON Output

I've been developing a web app on top of Django and I use the Django Rest Framework for my API. There's a model class named Events and my EventsSerializer in DRF is a pretty common serializer without any special configuration. It just dumps data returned by the EventManager.
There is a field "type" in the Event model class.
My json returned now is:
{
events: [
{object1},
{object2},
.....
]
}
, as anything dumped in a DRF api and returned by django.
For some reason, I need my events objects returned categorized by the "type" field. For example, I need to get this:
{
events: [
type1: [{object1}, {object2},...],
type2: [{object3}, {object4}, ...],
.......
]
}
I have literally searched anything related to that but couldn't find a proper solution. Do you have anything to suggest about that?
Thanks in advance
You can use SerializerMethodField and provide custom serialization logic there:
class EventSerializer(serializers.Serializer):
events = serializers.SerializerMethodField(source="get_events")
def get_events(self, events):
event_list = {}
return [event_list[e.type].add({e}) if event.type in event_list else event_list[event.type] = [] for event in events]
I had a model similar to the following:
class Book(models.Model):
title = models.CharField(max_length=200)
class Author(models.Model):
name = models.CharField(max_length=200)
books = models.ManyToManyField(Book)
The JSON that was being generated for an Author looks like this:
{
"name": "Sir Arthur C. Clarke",
"books": [
{
"title": "Rendezvous with Rama",
},
{
"title": "Childhood's End",
}
]
}
In the JSON wanted the books to be sorted by title. Since the books are pulled into the queryset via a prefetch_related adding an order_by to the View's queryset had no effect (generated SQL didn't have a join to the Books table). The solution I came up with was to override the get method. In my version of the get method, I have the super class generate the Response and I modify it's data (a Python dict) before returning it as shown below.
I'm not too worried about performance for two reasons:
because of the prefetch_related the join is already being done in Python rather than in the database
In my case the number of Books per Author is relatively small
class AuthorView(RetrieveUpdateAPIView):
queryset = Author.objects.prefetch_related(
'books',
)
serializer_class = AuthorSerializer
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
def key_func(book_json):
return book_json.get('title', '')
books = response.data.get('books', [])
books = sorted(books, key=key_func)
response.data['books'] = books
return response

Django/Tastypie - DELETE requests deleting everything

I have the following model
class Open(models.Model):
name=models.TextField()
opened=models.DateTimeField(auto_now_add=True)
user=models.ForeignKey(User)
and the following resources
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
class OpenResource(ModelResource):
user = fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Open.objects.all()
resource_name = 'open'
I'm trying to delete an Open object from some User's open_set.
For posting, I use the following code (using Requests):
content={"name":file_path,
"user":"/api/v1/user/2/"}
requests.post(
url='http://localhost:8000/api/v1/open/',
data=json.dumps(content),
headers={'content-type':'application/json'},
)
which works perfectly and does exactly what I want.
However, when trying to use similar code for deleting:
content={"name":file_path,
"user":"/api/v1/user/2/"}
requests.delete(
url='http://localhost:8000/api/v1/open/',
data=json.dumps(content),
headers={'content-type':'application/json'},
)
it just deletes all the Open objects from that user (in this case, user with id=2), instead of deleting only the Open objects whose "name" is file_path and whose "user" is "/api/vi/user/2/"
What am I missing?
Distinction between list and detail.
The RESTful methods are split into two kinds:
detail (for GET, PUT and DELETE):
/api/v1/objects/1/
and list (for GET, PUT and DELETE):
/api/v1/objects/
POST and PATCH are bit different.
Means that DELETE /api/v1/objects/ will remove all objects.
To delete one object you have to provide path with id:
DELETE /api/v1/objects/1/
Link to documentation
How filtering works in Tastypie:
You cannot just add things to content and wish to be picked up by Tastypie. All not meant to be there information will be ignored by Tastypie.
If you want to filter your list use queryset parameters:
/api/v1/objects/?name=asdfasdf&user=2
And allow filtering of these:
from tastypie.constants import ALL, ALL_WITH_RELATIONS
class Open(models.Model):
name=models.TextField()
opened=models.DateTimeField(auto_now_add=True)
user=models.ForeignKey(User)
filtering = {'name': ALL, 'user': ALL_WITH_RELATIONS}
After these changes you will be able to delete set of objects:
DELETE /api/v1/objects/?name=asdfasdf&user=5
Link to documentation
Edit:
So your call will look like this:
import urllib
content={"name":file_path,
"user":"/api/v1/user/2/"} # If doesn't work change '/api/v1/user/2/' into 2 I am not sure about this
url = 'http://localhost:8000/api/v1/open/?' + urllib.urlencode(content)
requests.delete(
url=url,
data=None,
headers={'content-type':'application/json'},
)

Can model views in Flask-Admin hyperlink to other model views?

Let's suppose we have a model, Foo, that references another model, User - and there are Flask-Admin's ModelView for both.
On the Foo admin view page
I would like the entries in the User column to be linked to the corresponding User model view.
Do I need to modify one of Flask-Admin's templates to achieve this?
(This is possible in the Django admin interface by simply outputting HTML for a given field and setting allow_tags (ref) True to bypass Django's HTML tag filter)
Some example code based on Joes' answer:
class MyFooView(ModelView):
def _user_formatter(view, context, model, name):
return Markup(
u"<a href='%s'>%s</a>" % (
url_for('user.edit_view', id=model.user.id),
model.user
)
) if model.user else u""
column_formatters = {
'user': _user_formatter
}
Use column_formatters for this: https://flask-admin.readthedocs.org/en/latest/api/mod_model/#flask.ext.admin.model.BaseModelView.column_formatters
Idea is pretty simple: for a field that you want to display as hyperlink, either generate a HTML string and wrap it with Jinja2 Markup class (so it won't be escaped in templates) or use macro helper: https://github.com/mrjoes/flask-admin/blob/master/flask_admin/model/template.py
Macro helper allows you to use custom Jinja2 macros in overridden template, which moves presentational logic to templates.
As far as URL is concerned, all you need is to find endpoint name generated (or provided) for the User model and do url_for('userview.edit_view', id=model.id) to generate the link.
extra information for #wodow, notice that model.user is wrong if you use pymongo as the backend, because the model in pymongo is a dict type, you can just use model['name'] to replace it
Adding this code to each of your models that have referenced by other models and flask-admin and jinja will take care of the name you want to display on the screen, just replace that with whatever you prefer:
def __unicode__(self):
return self.name # or self.id or whatever you prefer
for example:
class Role(db.Document, RoleMixin):
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
def __unicode__(self):
return self.name
class MasterUser(db.Document, UserMixin):
email = db.StringField(max_length=255)
password = db.StringField(max_length=255)
active = db.BooleanField(default=True)
confirmed_at = db.DateTimeField()
roles = db.ListField(db.ReferenceField(Role), default=[])

Categories