I'm adding new applications to an existing Django project, and encountered something I am unable to debug. The callback error is "AttributeError: 'QuerySet' object has no attribute 'META'", but I'm not sure that's what's going on. I've written 4 of these apps for different API objects today, and had no issues. When I add print() debug messages to the functions, what I find is that the execution seems to be jumping out of my lead_lists view function and into my lists view function before it errors.
This is my list.views.py
def list(request):
print("we're in lists view")
lists = List.objects.all()
print("lists saved for context")
context = {'lists': lists}
print("context created")
return render(request, 'list.html', context) # fails here
def load_lists(request):
api_lists = services.get_lists()
print("api lists loaded")
db_list_ids = list(List.objects.values_list('list_id', flat=True)) # jumps out of function here
print("db lists loaded") # this never prints
print(f"db_list_ids: {db_list_ids}")
for api_list in api_lists:
if api_list['id'] not in db_list_ids:
api_list = services.transform_list(api_list)
form = ListModelForm(api_list)
if form.is_valid():
form.save
else:
return HttpResponse(form.errors)
print("exited load loop")
lists = List.objects.all()
print("load lists objects saved")
context = {'lists': lists}
print("load lists context saved")
return render(request, 'load_lists.html', context)
The expected result is, when I navigate to /list/load it runs the load_lists view function. Here is the output I get from the console.
we're in lists view
lists saved for context
context created
[31/Jul/2019 16:20:32] "GET /list/ HTTP/1.1" 200 2458
api lists loaded
we're in lists view
lists saved for context
context created
Internal Server Error: /list/load/
Traceback (most recent call last):
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\David.Wilcox\ongage-django\ongage\list\views.py", line 19, in load_lists
db_list_ids = list(List.objects.values_list('list_id', flat=True))
File "C:\Users\David.Wilcox\ongage-django\ongage\list\views.py", line 14, in list
return render(request, 'list.html', context)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\shortcuts.py", line 36, in render
content = loader.render_to_string(template_name, context, request, using=using)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\template\loader.py", line 62, in render_to_string
return template.render(context, request)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\template\backends\django.py", line 61, in render
return self.template.render(context)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\template\base.py", line 169, in render
with context.bind_template(self):
File "c:\users\david.wilcox\appdata\local\programs\python\python37-32\Lib\contextlib.py", line 112, in __enter__
return next(self.gen)
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\template\context.py", line 246, in bind_template
updates.update(processor(self.request))
File "C:\Users\David.Wilcox\ongage-django\venv\lib\site-packages\django\template\context_processors.py", line 40, in debug
if settings.DEBUG and request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS:
AttributeError: 'QuerySet' object has no attribute 'META'
[31/Jul/2019 16:20:36] "GET /list/load/ HTTP/1.1" 500 103930
I originally thought it wasn't playing nicely due to the usage of the word 'list', so I refactored and renamed my variables, but the error is the same.
Short answer: please do not name your views after Python builtins like list. You can rename the view function to view_lists.
You here defined a function named list. As a result, if you later use list(List.objects.values_list('list_id', flat=True)) in your load_lists view, you will indeed call the view function, and not the builtin function, since that identifier now points to your view function.
You can for example rename it to view_lists, like:
# rename view function
def view_lists(request):
return render(request, 'list.html', {'lists': List.objects.all()})
def load_lists(request):
api_lists = services.get_lists()
db_list_ids = list(List.objects.values_list('list_id', flat=True))
print(f"db_list_ids: {db_list_ids}")
for api_list in api_lists:
if api_list['id'] not in db_list_ids:
api_list = services.transform_list(api_list)
form = ListModelForm(api_list)
if form.is_valid():
form.save()
else:
return HttpResponse(form.errors)
lists = List.objects.all()
return render(request, 'load_lists.html', context = {'lists': lists})
Note that you need to call the save() function on your form, so form.save(), not form.save.
PEP-8 recommends adding a trailing underscore if there is no better name than a builtin one:
If a function argument's name clashes with a reserved keyword, it is generally better to append a single trailing underscore rather than use an abbreviation or spelling corruption. Thus class_ is better than clss. (Perhaps better is to avoid such clashes by using a synonym.)
(...)
If your public attribute name collides with a reserved keyword, append a single trailing underscore to your attribute name. This is preferable to an abbreviation or corrupted spelling. (However, notwithstanding this rule, 'cls' is the preferred spelling for any variable or argument which is known to be a class, especially the first argument to a class method.)
Related
I was successfully pulling tweets from the Twitter API until I decided to put the keys/tokens in a separate configuration file. As I plan on uploading the main file to Github.
The solutions on StackOverflow that I found so far didn't solve my problem, unfortunately.
import oauth2 as oauth
import json
import configparser
config = configparser.RawConfigParser()
configpath = r'config.py'
config.read(configpath)
consumer_key = config.get('logintwitter', 'consumer_key')
consumer_secret = config.get('logintwitter', 'consumer_secret')
access_key = config.get('logintwitter', 'access_key')
access_secret = config.get('logintwitter', 'access_secret')
consumer = oauth.Consumer(key=consumer_key, secret=consumer_secret) #twitter: sign me in
access_token = oauth.Token(key=access_key, secret=access_secret) #grant me access
client = oauth.Client(consumer, access_token) #object return
timeline_endpoint = "https://api.twitter.com/1.1/statuses/home_timeline.json"
response, data = client.request(timeline_endpoint)
tweets = json.loads(data) #take a JSON string convert it to dictionary structure:
for tweet in tweets:
print(tweet["text"])
This is the error message:
Traceback (most recent call last):
File
"/Users/myname/PycharmProjects/twiiter2/twitterconnect.py", line 24,
in
print(tweet["text"]) TypeError: string indices must be integers
I tried changing the json.loads() method as well as the content in print(tweet["text"])
Humbled for anyon to point me in the right direction.
Thank you!
DjangoRestFramework seems to handle errors with a variety of ways. The ValidationError in the serializer class does not consistently return JSON the same.
Current response includes a JSON list/object string:
{"detail":["Unable to log in with provided credentials."]}
Looking to achieve:
{"detail":"Unable to log in with provided credentials."}
I realize that this response is a result of default functions. However, I've overridden the validate function:
class AuthCustomTokenSerializer(serializers.Serializer):
username = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True)
token = serializers.CharField(read_only=True)
def validate(self, validated_data):
username = validated_data.get('username')
password = validated_data.get('password')
# raise serializers.ValidationError({'detail': 'Unable to log in with provided credentials.'})
if username and password:
user = authenticate(phone_number=username, password=password)
try:
if UserInfo.objects.get(phone_number=username):
userinfo = UserInfo.objects.get(phone_number=username)
user = User.objects.filter(user=userinfo.user, password=password).latest('date_joined')
if user:
if user.is_active:
validated_data['user'] = user
return validated_data
else:
raise serializers.ValidationError({"detail": "User account disabled."})
except UserInfo.DoesNotExist:
try:
user = User.objects.filter(email=username, password=password).latest('date_joined')
if user.is_active:
validated_data['user'] = user
return validated_data
except User.DoesNotExist:
#raise serializers.ValidationError("s")
raise serializers.ValidationError({'detail': 'Unable to log in with provided credentials.'})
else:
raise serializers.ValidationError({"detail" : "Must include username and password."})
class Meta:
model = Token
fields = ("username", "password", "token")
I've tried adding a custom exception handler:
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.status_code
return response
views.py: if serializer.is_valid(raise_exception=True):
However, that only appends the currently raised error:
{"detail":["Unable to log in with provided credentials."],"status_code":400}
How should I use change the format of the returning text?
It only returns the JSON like this for this particular serializer within the validate function.
I've also looked into formatting the non_field_errors template, but it works with all my other serializers e.g:
{"detail": "Account exists with email address."}
Maybe you should try overriding json renderer class and hook up a custom one, where you can check for status code and detail key in response data, then re-format the value appropriately.
I never tried that, so I can't give you the exact codebase, but this is the only approach I can think of to have consistent response.
I am having real difficulty working out how I can edit the html and css on a live site via logging into the site as a privileged user in Django (if this is possible in Flask I may use it instead). For example modifying the background image used on a page. ckeditor allow you to do this for model fields:
class Post(models.Model):
content = RichTextField()
But not for the static html or css. How do people normally do this - make all changes on a test machine the push the .html and css to the live site? Ideally I want the designer to be able to log in and modify the site with a wysiwyg tool without needing a developer.
If you want to achieve editing of the layout files of the site like Wordpress does it for themes, you are going to need to implement an app to do that yourself, I'm not aware of any existing project that allows you to do that, in Django or in Flask.
In a nutshell, you need to pick out what files you want to expose and have a view where you load up the text file open(file), display it in a Django Form in a textarea, and save it back to the file again.
If you're editing css files, depending on your setup, you might need to trigger a collectstatic command on form save, so that the file goes where it is needed.
You could also use Ace Editor for easier code editing.
This is a stripped down example of what I used in a previous project for achieving this:
class EditFileView(FormView):
template_name = "file_edit_form.html"
form_class = EditFileForm
ALLOWED_ROOTS = ["%s/" % DISPLAY_TEMPLATES_DIRECTORY, ]
def get_allowed_roots(self):
return self.ALLOWED_ROOTS
def dispatch(self, request, *args, **kwargs):
if "file_name" not in request.GET:
raise Http404
file_name = request.GET['file_name']
exists = False
for root in self.get_allowed_roots():
exists = os.path.exists(os.path.join(root, file_name))
if exists:
self.file_path = os.path.join(root, file_name)
self.file_name = file_name
self.root = root
break
if not exists:
logger.debug(u"EditFileView: Could not find file to edit - %s" % file_name)
raise Http404()
return super(EditFileView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
try:
f = open(self.file_path, "wt")
f.write(form.cleaned_data['file_contents'])
f.close()
except Exception, e:
pass
return HttpResponseRedirect(self.get_success_url())
def get_initial(self):
initial = super(EditFileView, self).get_initial()
with open("%s" % self.file_path, "r") as f:
initial['file_contents'] = f.read()
initial["file_path"] = self.file_path
return initial
def get_success_url(self):
return reverse("edit_file") + "?file_name=%s" % self.file_name
def get_context_data(self, **kwargs):
current = super(EditFileView, self).get_context_data(**kwargs)
current.update({"file_name": self.file_path[len(self.root):].replace("//", "/")})
return current
i want to have custom json response after data post sendind to my Tastypie API models django.
class MyModelResource(ModelResource):
my_field=""
class Meta:
queryset = MyModel.objects.all()
resource_name = 'nick_name'
authentication = ApiKeyAuthentication()
authorization = DjangoAuthorization()
def hydrate(self, bundle):
#on recupere les donnée injectée par bundle.data['title']
#et on inject les donnée via bundle.obj.title
#bundle.data['my_field'] ="1234"
bundle.obj.my_field=bundle.data['my_field']
self.my_field = bundle.data['my_field']
return bundle
def wrap_view(self, view):
"""
Wraps views to return custom error codes instead of generic 500's
"""
#csrf_exempt
def wrapper(request, *args, **kwargs):
try:
callback = getattr(self, view)
response = callback(request, *args, **kwargs)
if request.is_ajax():
patch_cache_control(response, no_cache=True)
lst_dic=[]
mon_dic = dict(success=True, my_field=self.my_field
)
# response is a HttpResponse object, so follow Django's instructions
# to change it to your needs before you return it.
# https://docs.djangoproject.com/en/dev/ref/request-response/
lst_dic.append(mon_dic)
response = HttpResponse(simplejson.dumps(lst_dic), content_type='application/json')
return response
except (BadRequest, fields.ApiFieldError), e:
return HttpBadRequest({'success':False,'code': 666, 'message':e.args[0]})
except ValidationError, e:
# Or do some JSON wrapping around the standard 500
return HttpBadRequest({'success':False,'code': 777, 'message':', '.join(e.messages)})
except Exception, e:
# Rather than re-raising, we're going to things similar to
# what Django does. The difference is returning a serialized
# error message.
return self._handle_500(request, e)
return wrapper
My problem here, i can't grab the self.my_field value to put in mon_dic, i always have data object, not value...
thx for help
EDIT : Add my_field global variable, and then grab value from bundle that's it ;)
Maybe I am not understanding what you want to do here. But wrap_view is for handling customer error responses. If all you want to do is return the data that was posted, you can set always_return_data to true in your Meta:
class Meta:
always_return_data = True
Or if you want to control what data gets sent back, you can use the dehydrate method:
def dehydrate(self, bundle):
bundle.data['custom_field'] = "Whatever you want"
return bundle
I modified the https://developers.google.com/drive/v2/reference/files/list example to get a list of all file items, using request partial resources.
def retrieve_all_files(service, fields = None):
"""Retrieve a list of File resources.
Args:
service: Drive API service instance.
fields: The fields (of each files Resource item!), e.g. 'id,title'
if not passed, get everything.
Returns:
List of File resources.
"""
result = []
page_token = None
while True:
try:
param = {}
if fields:
param['fields'] = 'nextPageToken,items(' + fields + ')'
if page_token:
param['pageToken'] = page_token
files = service.files().list(**param).execute()
result.extend(files['items'])
page_token = files.get('nextPageToken')
if not page_token:
break
except errors.HttpError, error:
print 'An error occurred: %s' % error
break
return result
In my test Drive folder I have a file that has a description string set. When I run
retrieve_all_files(drive_service)
without setting the fields parameter (so get everything), sure enough in the list of items, the file looks fine:
[{u'alternateLink': u'...',...
u'description': u'Description',
u'id': u'...',...},...]
but running
retrieve_all_files(drive_service, fields='id,description,title')
yields a list where none of the items have the 'description' property. Not all files have the 'description' property set; is this why the partial resource response is omitting the field even from items that have the property? I'm not sure if this is an undocumented feature that I didn't expect or a bug.
I was able to reproduce your issue. When using an authorized scopes list of:
https://www.googleapis.com/auth/drive
which should be full access, I was not able to get the description field returned. However, I found that if I also included the scope:
https://www.googleapis.com/auth/drive.appdata
the description field would return properly.
Can you confirm on your end that adding the above to your scopes resolves the issue? Seems like a bug on Google's end.
This was confirmed as a bug and a fix should be available in a few days.