I'm trying to use for the first time ElasticSearch in my web application and I have difficulties with my ES indexation.
The process is :
When I add a document with upload field, the document is indexed by ES
I have a function which let to define a new document title
I have to reindex this document with new title in order to appear in ES list of documents
I have some issues with this last part and I would like to know if you could help me.
Actual processus :
models.py file :
class Document(EdqmFullTable):
CAT_CHOICES = (...)
...
file = models.FileField(upload_to=upload_file)
def get_filename(self):
return os.path.join(settings.MEDIA_ROOT, str(self.file))
When a new document is added through this model, it calls ES methods :
es4omcl.py file :
class EdqmES(object):
host = 'localhost'
port = 9200
es = None
def __init__(self, *args, **kwargs):
self.host = kwargs.pop('host', self.host)
self.port = kwargs.pop('port', self.port)
# Connect to ElasticSearch server
self.es = Elasticsearch([{
'host': self.host,
'port': self.port
}])
def __str__(self):
return self.host + ':' + self.port
#staticmethod
def file_encode(filename):
with open(filename, "rb") as f:
return b64encode(f.read()).decode('utf-8')
def create_pipeline(self):
body = {
"description": "Extract attachment information",
"processors": [
{"attachment": {
"field": "data",
"target_field": "attachment",
"indexed_chars": -1
}},
{"remove": {"field": "data"}}
]
}
self.es.index(
index='_ingest',
doc_type='pipeline',
id='attachment',
body=body
)
def index_document(self, doc, bulk=False):
filename = doc.get_filename()
try:
data = self.file_encode(filename)
except IOError:
data = ''
print('ERROR with ' + filename)
# TODO: log error
item_body = {
'_id': doc.id,
'data': data,
'relative_path': str(doc.file),
'title': doc.title,
}
if bulk:
return item_body
result1 = self.es.index(
index='omcl', doc_type='annual-report',
id=doc.id,
pipeline='attachment',
body=item_body,
request_timeout=60
)
print(result1)
return result1
And signals from callback.py file when a new document is saved into the database :
#receiver(signals.post_save, sender=Document, dispatch_uid='add_new_doc')
def add_document_handler(sender, instance=None, created=False, **kwargs):
""" When a document is created index new annual report (only) with Elasticsearch and update conformity date if the
document is a new declaration of conformity
:param sender: Class which is concerned
:type sender: the model class
:param instance: Object which was just saved
:type instance: model instance
:param created: True for a creation, False for an update
:type created: boolean
:param kwargs: Additional parameter of the signal
:type kwargs: dict
"""
if not created:
return
# Update Conformity declaration date
if instance.category == Document.OPT_CD:
now = datetime.today()
Omcl.objects.filter(id=instance.omcl_id).update(last_conformity=now)
# Index only annual reports
elif instance.category == Document.OPT_ANNUAL:
es = EdqmES()
es.index_document(instance)
My process :
I defined a new class which let to handle document as soon as they are uploaded. I can modify the document title. So the last step is : reindex this modified document with ES.
class ManageDocView(AdminRequiredMixin, View, BaseException):
""" Render the Admin Manage documents to update year in the filename"""
template_name = 'omcl/manage_doc_form.html'
form_class = ManageDocForm
success_url = 'omcl/manage_doc_form.html'
def get(self, request):
form = self.form_class()
context = {
"form": form
}
return render(request, self.template_name, context)
def post(self, request):
form = self.form_class()
query_document_updated = None
query_omcl = None
query_document = None
if "SearchOMCL" in request.POST:
omcl_list = request.POST['omcl_list']
query_omcl = Omcl.objects.get(id=omcl_list)
query_document = Document.objects.filter(omcl=omcl_list)
elif "UpdateDocument" in request.POST:
checkbox_id = request.POST['DocumentChoice']
checkbox_id_minus_1 = int(checkbox_id) - 1
query_document_updated = Document.objects.get(id=checkbox_id)
print(query_document_updated.id)
omclcode = query_document_updated.omcl.code
src_filename = query_document_updated.src_filename
filename, file_extension = os.path.splitext(src_filename)
category = query_document_updated.category
if category == "ANNUAL":
category = "ANNUAL_REPORT"
year = self.request.POST.get('q1year')
# Create the new document title updated by the new year
new_document_title = f"{year}_{category}_{omclcode}_{checkbox_id_minus_1} - {src_filename}"
# Create the new document file updated by the new year
new_document_file = f"omcl_docs/{omclcode}/{year}_{category}_{omclcode}_{checkbox_id_minus_1}{file_extension}"
# Get file.name in order to rename document file in /media/
document_path = query_document_updated.file.name
try:
actual_document_path = os.path.join(settings.MEDIA_ROOT, document_path)
new_document_path_temp = settings.MEDIA_ROOT + "/" + new_document_file
new_document_path = os.rename(actual_document_path, new_document_path_temp)
except FileNotFoundError:
messages.error(self.request, _(f"Document {src_filename} doesn't exist in the server"))
return redirect('manage_doc')
else:
# Assign modifications to selected document and save it into the database
query_document_updated.title = new_document_title
query_document_updated.file = new_document_file
query_document_updated.save()
messages.success(self.request, _(f"The modification has been taken account"))
context = {
'form': form,
'query_omcl': query_omcl,
'query_document': query_document,
'query_document_updated': query_document_updated,
}
return render(request, self.template_name, context)
I'm completely list because I don't know How I can adapt this part in callback.py file :
if not created:
return
with my Django ManageDocView() class
Related
I'm using Django for my work project. What I need to get is all likes put by some user. Likes are put to transactions, so I am getting the list of all transactions in 'items' field, where the user put his like/dislike. There are some optional parameters that can be in the request:
{
"user_id": (usually is taken from session but can be used to get likes of specific user by admins),
"like_kind_id": (1 for like/2 for dislike),
"include_code_and_name": (True/False),
"page_size": (integer number) for pagination
}
With the last parameter, I am facing the issues. The result should look like this:
{
"user_id":id,
"likes":[
{
"like_kind":{
"id":id,
"code":like_code,
"name":like_name,
},
"items":[
{
"transaction_id":transaction_id,
"time_of":date_created,
},...
]
}
]
}
"Likes" is just array of 2 elements (like and dislike).
So, my problem is that I cannot set pagination for "items" array as there can be a lot of transactions. I am getting the page_size from request.data.get("page_size") but cannot apply it so that the results will be paginated. I know that for GET-requests I need simply set pagination_class in views.py. Anyone knows how can I do the similar procedure for POST-request and set the page_size of pagination class to 'page_size' from data.request? =)
This is my views.py:
class LikesUserPaginationAPIView(PageNumberPagination):
page_size = 1
page_size_query_param = 'page_size'
max_page_size = 1
class LikesUserListAPIView(APIView):
"""
All likes of the user
"""
permission_classes = [IsAuthenticated]
authentication_classes = [authentication.SessionAuthentication,
authentication.TokenAuthentication]
pagination_class = LikesUserPaginationAPIView
#classmethod
def post(cls, request, *args, **kwargs):
like_kind = request.data.get('like_kind')
include_code = request.data.get('include_code')
page_size = request.data.get('page_size')
pagination.PageNumberPagination.page_size = page_size
if include_code is None:
include_code = False
else:
if include_code == "False":
include_code = False
elif include_code == "True":
include_code = True
else:
return Response("Should be True или False for include_code",
status=status.HTTP_400_BAD_REQUEST)
if like_kind is None:
like_kind = "all"
else:
try:
likekind = LikeKind.objects.get(id=like_kind)
except LikeKind.DoesNotExist:
return Response("ID doesn't exists ",
status=status.HTTP_404_NOT_FOUND)
context = {"include_code": include_code, "like_kind": like_kind}
user_id = request.user.id # get the user_id from session
# TODO
# optionally other users with higher privilege can get access to other users' likes
# need to check for the privilege (admin, coordinator, ..)
# user_id = request.data.get('user_id')
if user_id is not None:
try:
user = User.objects.get(id=user_id)
users = [user, user, user, user, user]
serializer = LikeUserSerializer(users, many=True, context=context)
return Response(serializer.data)
except User.DoesNotExist:
return Response("ID doesn't exists ",
status=status.HTTP_404_NOT_FOUND)
return Response("user_id is not given",
status=status.HTTP_400_BAD_REQUEST)
My serializer.py:
class LikeUserSerializer(serializers.ModelSerializer):
likes = serializers.SerializerMethodField()
user_id = serializers.SerializerMethodField()
def get_user_id(self, obj):
return obj.id
def get_likes(self, obj):
include_code = self.context.get('include_code')
like_kind_id = self.context.get('like_kind')
likes = []
if like_kind_id == "all":
like_kinds = [(like_kind.id, like_kind.name, like_kind.get_icon_url()) for like_kind in LikeKind.objects.all()]
else:
like_kinds = [(like_kind.id, like_kind.name, like_kind.get_icon_url()) for like_kind in
[LikeKind.objects.get(id=like_kind_id)]]
# {"user_id": 1}
if include_code:
for like_kind in like_kinds:
items = []
transactions_liked = [(like.transaction_id, like.date_created)
for like in Like.objects.filter_by_user_and_like_kind(obj.id, like_kind[0])]
for transaction1 in transactions_liked:
items.append(
{
"transaction_id": transaction1[0],
"time_of": transaction1[1],
}
)
likes.append(
{
"like_kind": {
'id': like_kind[0],
'name': like_kind[1],
'icon': like_kind[2],
},
"items": items
}
)
return likes
else:
for like_kind in like_kinds:
items = []
transactions_liked = [(like.transaction_id, like.date_created)
for like in Like.objects.filter_by_user_and_like_kind(obj.id, like_kind[0])]
for transaction1 in transactions_liked:
items.append(
{
"transaction_id": transaction1[0],
"time_of": transaction1[1],
}
)
likes.append(
{
"like_kind": {
'id': like_kind[0],
},
"items": items
}
)
return likes
class Meta:
model = Like
fields = ['user_id', 'likes']
In urls.py I have the following path:
path('get-likes-by-user/', likes_views.LikesUserListAPIView.as_view()),
If you have any questions, please ask. Thanks, all!! I will appreciate your help!
Im new in Django,
I'm using xhtml2pdf for rendering data objects from template to PDF file , and i want to allow users to render only checked objects by checkboxes into pdf, is there anyway to filter that in Django ?
Thanks
views.py :
def render_to_pdf(template_src, context_dict={}):
template = get_template(template_src)
html = template.render(context_dict)
result = BytesIO()
pdf = pisa.pisaDocument(BytesIO(html.encode('ISO-8859-1')), result, link_callback=link_callback)
if pdf.err:
return HttpResponse('Error')
return HttpResponse(result.getvalue(), content_type='application/pdf')
class ViewPDF(View):
#method_decorator(login_required(login_url='livraison:login_page'))
def get(self, request, *args, **kwargs):
checked_objects = [i dont know how to get them]
queryset = Livraison.objects.filter(id__in=[checked_objects]) # i want something does that
data = {
'livraisons' : queryset,
'now' : f'Livraisons-{date.today()}'
}
pdf = render_to_pdf('pdf_template.html', data)
return HttpResponse(pdf, content_type='application/pdf')
use forms to get user data.
class LivraisonSelectForm(forms.Form):
livraison = forms.ModelChoiceField(label='select', queryset=Livraison.objects.all(), widget=forms.CheckboxInput())
then use it in your view:
class ViewPDF(FormView):
form_class = LivraisonSelectForm
template_name = 'path_to_template_with_html_to_filter_data'
#method_decorator(login_required(login_url='livraison:login_page'))
def form_valid(self, form):
checked_objects = form.cleaned_data.get('livraison', [])
queryset = Livraison.objects.filter(id__in=checked_objects) # i want something does that
data = {
'livraisons' : queryset,
'now' : f'Livraisons-{date.today()}'
}
pdf = render_to_pdf('pdf_template.html', data)
return HttpResponse(pdf, content_type='application/pdf')
and don't forget to show this form in your html {{ form }}
**
UPDATE
**
I Found a solution , i used Ajax code to collect every selected object
// check selected objects (#pdf is the id of the button which gonna generate the pdf file)
$('#pdf').click(function () {
var id = [];
$(':checkbox:checked').each(function (i) {
id[i] = $(this).val()
})
if (id.length === 0) {
alert('Please Select something..');
} else {
$.ajax({
url: '.',
method: 'GET',
data: {
id,
},
success: function (response) {
console.log('PDF is ready')
}
})
}
});
then i ad the variable into the view function to collect the selected objects and filter the queryset
myFunction.selected_ones = request.GET.getlist('id[]')
then filter queryset in the generated_pdf_func
queryset = MODEL.objects.filter(id__in=[x for x in myFunction.selected_ones if x != '0'])
NOTE : <if x != '0'> is important in case you want to select all because it returns 0 and you don't have any id = 0 in the DB
I'm trying to upload a file to a django backend using graphene-file-upload which has the mutation to link the backend to the react frontend where I'm trying to use apollo-upload client to link it with graphql.
In my django model an empty file is being successfully uploaded but it is not uploading the real file which I'm selecting but uploads an empty file.
Like it uploads nothing {} but the instance is created in the database where another story is added which is empty.
Here is some of my code.
My Database Model. models.py
class Story(models.Model):
file = models.FileField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
MY schema.py
from graphene_file_upload.scalars import Upload
class StoryType(DjangoObjectType):
class Meta:
model = Story
class UploadFile(graphene.Mutation):
story = graphene.Field(StoryType)
class Arguments:
file = Upload()
def mutate(self, info, file):
for line in file:
print(line)
story = Story(file=file)
story.save()
return UploadFile(story=story)
My frontend File.js
import React from 'react';
import { Mutation } from 'react-apollo';
import {withStyles} from '#material-ui/core/styles';
import gql from 'graphql-tag';
const styles = theme => ({
layoutRoot: {}
});
const UploadFile = () => (
<Mutation
mutation={gql`
mutation($file: Upload!) {
uploadFile(file: $file) {
story {
file
}
}
}
`}
>
{mutate => (
<input
type="file"
required
onChange={({
target: {
validity,
files: [file]
}
}) => validity.valid && mutate({ variables: { file } })}
/>
)}
</Mutation>
)
export default withStyles(styles, {withTheme: true})(UploadFile);
It's working for me now I've overridden parse_body in GraphQLView, in order for it to deal with multipart/form-data correctly.
# views.py
from django.http.response import HttpResponseBadRequest
from graphene_django.views import GraphQLView
class MyGraphQLView(GraphQLView):
def parse_body(self, request):
content_type = self.get_content_type(request)
if content_type == "application/graphql":
return {"query": request.body.decode()}
elif content_type == "application/json":
# noinspection PyBroadException
try:
body = request.body.decode("utf-8")
except Exception as e:
raise HttpError(HttpResponseBadRequest(str(e)))
try:
request_json = json.loads(body)
if self.batch:
assert isinstance(request_json, list), (
"Batch requests should receive a list, but received {}."
).format(repr(request_json))
assert (
len(request_json) > 0
), "Received an empty list in the batch request."
else:
assert isinstance(
request_json, dict
), "The received data is not a valid JSON query."
return request_json
except AssertionError as e:
raise HttpError(HttpResponseBadRequest(str(e)))
except (TypeError, ValueError):
raise HttpError(HttpResponseBadRequest("POST body sent invalid JSON."))
# Added for graphql file uploads
elif content_type == 'multipart/form-data':
operations = json.loads(request.POST['operations'])
files_map = json.loads(request.POST['map'])
return place_files_in_operations(
operations, files_map, request.FILES)
elif content_type in [
"application/x-www-form-urlencoded",
#"multipart/form-data",
]:
return request.POST
return {}
def place_files_in_operations(operations, files_map, files):
# operations: dict or list
# files_map: {filename: [path, path, ...]}
# files: {filename: FileStorage}
fmap = []
for key, values in files_map.items():
for val in values:
path = val.split('.')
fmap.append((path, key))
return _place_files_in_operations(operations, fmap, files)
def _place_files_in_operations(ops, fmap, fobjs):
for path, fkey in fmap:
ops = _place_file_in_operations(ops, path, fobjs[fkey])
return ops
def _place_file_in_operations(ops, path, obj):
if len(path) == 0:
return obj
if isinstance(ops, list):
key = int(path[0])
sub = _place_file_in_operations(ops[key], path[1:], obj)
return _insert_in_list(ops, key, sub)
if isinstance(ops, dict):
key = path[0]
sub = _place_file_in_operations(ops[key], path[1:], obj)
return _insert_in_dict(ops, key, sub)
raise TypeError('Expected ops to be list or dict')
def _insert_in_dict(dct, key, val):
return {**dct, key: val}
def _insert_in_list(lst, key, val):
return [*lst[:key], val, *lst[key+1:]]
Requirements
from python_graphql_client import GraphqlClient
import asyncio
client = GraphqlClient(endpoint="http://localhost:8000/graphql")
Client-side -
def test_archive_upload(client):
file_header = "data:application/zip;base64,"
with open("files/Archive.zip", 'rb') as file:
query = """
mutation uploadFile($file: Upload) {
uploadFile(file:$file) {
ok
}
}
"""
file = file.read()
file = file_header + base64.b64encode(file).decode("UTF-8")
variables = {"file": file}
data = client.execute(query=query, variables=variables)
Run -
asyncio.get_event_loop().run_until_complete(test_archive_upload(client))
Server-side -
file = file.split(",")
file[0] = file[0]+","
file_type = guess_extension(guess_type(file[0])[0])
file = base64.b64decode(file[1])
with open("files/test"+ file_type, "wb") as w_file:
w_file.write(file)
I'm trying to generate a custom HTML and I have a value I want to pass into xml.startElement (or root if you're thinking in generic terms). How do I go about doing this?
I'm currently using django rest framework a class view and a custom renderer -
This is the beginning of the renderer -
class TESTRenderer(renderers.BaseRenderer):
media_type = 'application/xml'
format = 'xml'
charset = 'utf-8'
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Renders *obj* into serialized XML.
"""
if data is None:
return ''
stream = StringIO()
xml = SimplerXMLGenerator(stream, self.charset)
xml.startDocument()
xml.startElement(header.data, {})
So as you can see I'm trying to pass a variable called header into the xml.startElement
Here's the view where that data lies -
class TestDetail(APIView):
permission_classes = (AllowAny,)
"""
Retrieve, update or delete a snippet instance.
"""
def get(self, request, pk, format=None):
jobmst_name = queryset1
nodmst_alias = queryset2
sysval_integer = queryset3
mst = queryset4
dtl = queryset5
dep = queryset6
trg = queryset7
name = str(jobmst_name)
master = str(nodmst_alias)
dbversion = str(sysval_integer)
header = 'job id="%s" name="%s" master="%s" dbversion="%s" xmlversion="1"' % (pk, name, master, dbversion)
jobmststring = JobmstSerializer(mst)
jobdtlstring = JobdtlSerializer(dtl)
jobdepstring = JobdepSerializer(dep, many=True)
trgjobstring = TrgjobSerializer(trg, many=True)
jobmst_serialized = {'jobmst': jobmststring.data}
jobdtl_serialized = {'jobdtl': jobdtlstring.data}
jobdep_serialized = [{'jobdep':item} for item in jobdepstring.data]
trgjob_serialized = [{'trgjob':item} for item in trgjobstring.data]
jobgroup = header, jobmst_serialized, jobdtl_serialized, jobdep_serialized, trgjob_serialized
return TestResponse(jobgroup)
The response it's using is here -
class TestResponse(HttpResponse):
"""
An HttpResponse that renders its content into XML.
"""
def __init__(self, data, **kwargs):
content = TESTRenderer().render(data)
kwargs['content_type'] = 'application/xml'
super(TestResponse, self).__init__(content, **kwargs)
Is there something I'm missing with the TestDetail where I should separate the header from the data?
maybe like this?
return TestResponse (header, jobgroup)
and then alter TestResponse to include?
def __init__(self, header, data, **kwargs):
I don't know python/django. but it seems the "Value" you are talking about are actually attributes you want to assign to the element node. I posted the same on your /r/django thread about this.
I'm attempting to build a very simple user permissions system with webapp2's auth library. I'm using gae-simpleauth to log users in with their Google account. I'm hoping to compare the user's email address to a list of permitted email addresses to determine if a user has access to a resource, but I'm not clear on how to get the email address from the Google account into the account on my app. Users are currently able to log in, but the email address doesn't seem to be something simpleauth adds to their account by default.
How can I retrieve the email address from Google and store it in my app's user profile using gae-simpleauth?
My implementation of gae-simpleauth is nearly identical to the example with the addition of the get_user_and_flags function which fetches the logged in user and sets the admin flag if the user's email is in a list in secrets.py. Unfortunately, that doesn't work because user doesn't have an email attribute.
# -*- coding: utf-8 -*-
import logging, secrets, webapp2
from google.appengine.api import users
from webapp2_extras import auth, sessions, jinja2
from jinja2.runtime import TemplateNotFound
from lib.simpleauth import SimpleAuthHandler
def get_user_and_flags(self):
"""Returns the current user and permission flags for that user"""
flags = {}
user = None
if self.logged_in:
user = self.current_user
flags = {
'admin': user.email in secrets.ADMIN_USERS,
}
return user, flags
def simpleauth_login_required(handler_method):
"""A decorator to require that a user be logged in to access a handler.
To use it, decorate your get() method like this:
#simpleauth_login_required
def get(self):
user = self.current_user
self.response.out.write('Hello, ' + user.name())
"""
def check_login(self, *args, **kwargs):
if self.request.method != 'GET':
self.abort(400, detail='The login_required decorator '
'can only be used for GET requests.')
if self.logged_in:
handler_method(self, *args, **kwargs)
else:
self.session['original_url'] = self.request.url.encode('ascii', 'ignore')
self.redirect('/login/')
return check_login
class BaseRequestHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def jinja2(self):
"""Returns a Jinja2 renderer cached in the app registry"""
return jinja2.get_jinja2(app=self.app)
#webapp2.cached_property
def session(self):
"""Returns a session using the default cookie key"""
return self.session_store.get_session()
#webapp2.cached_property
def auth(self):
return auth.get_auth()
#webapp2.cached_property
def current_user(self):
"""Returns currently logged in user"""
user_dict = self.auth.get_user_by_session()
return self.auth.store.user_model.get_by_id(user_dict['user_id'])
#webapp2.cached_property
def logged_in(self):
"""Returns true if a user is currently logged in, false otherwise"""
return self.auth.get_user_by_session() is not None
def render(self, template_name, template_vars={}):
# Preset values for the template
values = {
'url_for': self.uri_for,
'logged_in': self.logged_in,
'flashes': self.session.get_flashes()
}
# Add manually supplied template values
values.update(template_vars)
# read the template or 404.html
try:
self.response.write(self.jinja2.render_template(template_name, **values))
except TemplateNotFound:
self.abort(404)
def head(self, *args):
"""Head is used by Twitter. If not there the tweet button shows 0"""
pass
class ProfileHandler(BaseRequestHandler):
def get(self):
"""Handles GET /profile"""
if self.logged_in:
self.render('profile.html', {
'user': self.current_user,
'session': self.auth.get_user_by_session()
})
else:
self.redirect('/')
class AuthHandler(BaseRequestHandler, SimpleAuthHandler):
"""Authentication handler for OAuth 2.0, 1.0(a) and OpenID."""
# Enable optional OAuth 2.0 CSRF guard
OAUTH2_CSRF_STATE = True
USER_ATTRS = {
'facebook' : {
'id' : lambda id: ('avatar_url',
'http://graph.facebook.com/{0}/picture?type=large'.format(id)),
'name' : 'name',
'link' : 'link'
},
'google' : {
'picture': 'avatar_url',
'name' : 'name',
'link' : 'link'
},
'windows_live': {
'avatar_url': 'avatar_url',
'name' : 'name',
'link' : 'link'
},
'twitter' : {
'profile_image_url': 'avatar_url',
'screen_name' : 'name',
'link' : 'link'
},
'linkedin' : {
'picture-url' : 'avatar_url',
'first-name' : 'name',
'public-profile-url': 'link'
},
'foursquare' : {
'photo' : lambda photo: ('avatar_url', photo.get('prefix') + '100x100' + photo.get('suffix')),
'firstName': 'firstName',
'lastName' : 'lastName',
'contact' : lambda contact: ('email',contact.get('email')),
'id' : lambda id: ('link', 'http://foursquare.com/user/{0}'.format(id))
},
'openid' : {
'id' : lambda id: ('avatar_url', '/img/missing-avatar.png'),
'nickname': 'name',
'email' : 'link'
}
}
def _on_signin(self, data, auth_info, provider):
"""Callback whenever a new or existing user is logging in.
data is a user info dictionary.
auth_info contains access token or oauth token and secret.
"""
auth_id = '%s:%s' % (provider, data['id'])
logging.info('Looking for a user with id %s', auth_id)
user = self.auth.store.user_model.get_by_auth_id(auth_id)
_attrs = self._to_user_model_attrs(data, self.USER_ATTRS[provider])
if user:
logging.info('Found existing user to log in')
# Existing users might've changed their profile data so we update our
# local model anyway. This might result in quite inefficient usage
# of the Datastore, but we do this anyway for demo purposes.
#
# In a real app you could compare _attrs with user's properties fetched
# from the datastore and update local user in case something's changed.
user.populate(**_attrs)
user.put()
self.auth.set_session(
self.auth.store.user_to_dict(user))
else:
# check whether there's a user currently logged in
# then, create a new user if nobody's signed in,
# otherwise add this auth_id to currently logged in user.
if self.logged_in:
logging.info('Updating currently logged in user')
u = self.current_user
u.populate(**_attrs)
# The following will also do u.put(). Though, in a real app
# you might want to check the result, which is
# (boolean, info) tuple where boolean == True indicates success
# See webapp2_extras.appengine.auth.models.User for details.
u.add_auth_id(auth_id)
else:
logging.info('Creating a brand new user')
ok, user = self.auth.store.user_model.create_user(auth_id, **_attrs)
if ok:
self.auth.set_session(self.auth.store.user_to_dict(user))
# Remember auth data during redirect, just for this demo. You wouldn't
# normally do this.
self.session.add_flash(data, 'data - from _on_signin(...)')
self.session.add_flash(auth_info, 'auth_info - from _on_signin(...)')
# Go to the last page viewed
target = str(self.session['original_url'])
self.redirect(target)
def logout(self):
self.auth.unset_session()
self.redirect('/')
def handle_exception(self, exception, debug):
logging.error(exception)
self.render('error.html', {'exception': exception})
def _callback_uri_for(self, provider):
return self.uri_for('auth_callback', provider=provider, _full=True)
def _get_consumer_info_for(self, provider):
"""Returns a tuple (key, secret) for auth init requests."""
return secrets.AUTH_CONFIG[provider]
def _to_user_model_attrs(self, data, attrs_map):
"""Get the needed information from the provider dataset."""
user_attrs = {}
for k, v in attrs_map.iteritems():
attr = (v, data.get(k)) if isinstance(v, str) else v(data.get(k))
user_attrs.setdefault(*attr)
return user_attrs
Hope this help( I have same probblem )
First change in secrets.py in line:
'google': (GOOGLE_APP_ID, GOOGLE_APP_SECRET, 'https://www.googleapis.com/auth/userinfo.profile'),
to
'google': (GOOGLE_APP_ID,GOOGLE_APP_SECRET, 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'),
and in auth.py change USER_ATTRS =
{
...
'google' : {
'picture': 'avatar_url',
'email' : 'email', <-- new attr
'name' : 'name',
'link' : 'link'
},
}
Since your question includes no code snippet, I can only guess at what you have done so far. In light of that, the below code should work:
from google.appengine.api import users
user = users.get_current_user()
email = user.email()
Following the idea of nguyên, I add customize also the "_to_user_model_attrs" method.
Here my piece of code:
def _to_user_model_attrs(self, data, attrs_map):
"""Get the needed information from the provider dataset."""
user_attrs = {}
for k, v in attrs_map.iteritems():
if v =="email":
attr = (v, data.get(k)[0].get('value'))
else:
attr = (v, data.get(k)) if isinstance(v, str) else v(data.get(k))
user_attrs.setdefault(*attr)
return user_attrs
It works for me!
There seem to be several methods of authentication, and mix-matching does not work. If you havne't already, make sure you read through this, https://developers.google.com/appengine/articles/auth
There are a few sections that might be relative depending on what else you have been doing with Google.
Changes from the Google Apps account transition
Configuring Google Apps to Authenticate on Appspot