Django edit static content on live site - html

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

Related

How can I process json file and display some data in form of charts in django

Basically, I have implemented Sentiment analysis for the Amazon review dataset in python. Now I want to make a web app for it. I try to look on google for it but all the projects are for Twitter data. Till now I make a Django app which can take json file from the user and display the success message. Any ideas on how to load the json file in my script and put these data on the charts
Create a model to upload the file. Create a director upload for uploading at root of your project
from django.db import models
class JSONFile(models.Model):
file = models.FileField(upload_to='uploads')
You can use a form:
from django import forms
class JSONFileForm(forms.Form):
file = forms.FileField(
label='Select a file',
help_text='max. 42 megabytes'
)
Create templates as 'chart.html' to show your chart accordingly and 'upload.html' to render above form
In your views.py:
from forms import JSONFileForm
from models import JSONFile
from django.shortcuts import render
def chart(request):
if request.method == 'POST':
form = JSONFileForm(request.POST, request.FILES)
if form.is_valid():
newfile = JSONFile(file = request.FILES['file'])
newfile.save()
json.dumps(request.FILES['file'])
#format your data here
return render(request,"chart.html",{}) #pass data context in {} as dict
else:
form = JSONFileForm(request.POST)
else:
form = JSONFileForm()
return render(request,"upload.html",{"form":form})

YouTube analytics API rows empty

I know this question has been answered before, but I seem to have a different problem. Up until a few days ago, my querying of YouTube never had a problem. Now, however, every time I query data on any video the rows of actual video data come back as a single empty array.
Here is my code in full:
# -*- coding: utf-8 -*-
import os
import google.oauth2.credentials
import google_auth_oauthlib.flow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from google_auth_oauthlib.flow import InstalledAppFlow
import pandas as pd
import csv
SCOPES = ['https://www.googleapis.com/auth/yt-analytics.readonly']
API_SERVICE_NAME = 'youtubeAnalytics'
API_VERSION = 'v2'
CLIENT_SECRETS_FILE = 'CLIENT_SECRET_FILE.json'
def get_service():
flow = InstalledAppFlow.from_client_secrets_file(CLIENT_SECRETS_FILE, SCOPES)
credentials = flow.run_console()
#builds api-specific service
return build(API_SERVICE_NAME, API_VERSION, credentials = credentials)
def execute_api_request(client_library_function, **kwargs):
response = client_library_function(
**kwargs
).execute()
print(response)
columnHeaders = []
# create a CSV output for video list
csvFile = open('video_result.csv','w')
csvWriter = csv.writer(csvFile)
csvWriter.writerow(["views","comments","likes","estimatedMinutesWatched","averageViewDuration"])
if __name__ == '__main__':
# Disable OAuthlib's HTTPs verification when running locally.
# *DO NOT* leave this option enabled when running in production.
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
youtubeAnalytics = get_service()
execute_api_request(
youtubeAnalytics.reports().query,
ids='channel==UCU_N4jDOub9J8splDAPiMWA',
#needs to be of form YYYY-MM-DD
startDate='2018-01-01',
endDate='2018-05-01',
metrics='views,comments,likes,dislikes,estimatedMinutesWatched,averageViewDuration',
dimensions='day',
filters='video==ZeY6BKqIZGk,YKFWUX9w4eY,bDPdrWS-YUc'
)
You can see in the Reports: Query front page that you need to use the new scope:
https://www.googleapis.com/auth/youtube.readonly
instead of the old one:
https://www.googleapis.com/auth/yt-analytics.readonly
After changing the scope, perform a re-authentication (delete the old credentials) for the new scope to take effect.
This is also confirmed in this forum.
One of the mishaps may come if you chose wrong account/accounts during oAuth2 authorisation. For instance you may have to get "account" on the firs screen but then on second screen (during authorisation) use "brand account" and not the main account from the first step that also is on a list for second step.
I got the same problem and replacing with https://www.googleapis.com/auth/youtube.readonly scope doesn't work.
(Even making requests in the API webpage, it returns empty rows.)
Instead, using the https://www.googleapis.com/auth/youtube scope works fine in my case.

Django Rest Framework: what happened to my default Renderer?

I would like calls to /contacts/1.json to return json, 1.api to return browsableAPI, and calls with format=None aka /contacts/1/ to return a template where we call render_form. This way end-users can have pretty forms, and developers can use the .api format, and ajax/apps etc use .json. Seems like a common use case but something isn't clicking for me here in DRF...
Struggling with how DRF determines the Renderer used when no format is given. I found and then lost some info here on stack exchange that basically said to split the responses based on format. Adding the TemplateHTMLRenderer caused all sorts of pain. I had tried to split based on format but that is giving me JSON error below.
I don't understand the de facto way to define what renderer should be used. Especially when no format is provided. I mean, it "just works" when using Response(data). And I can get the TemplateHTMLRenderer to work but at the cost of having no default Renderer.
GET /contacts/1/ Gives the error:
<Contact: Contact object> is not JSON serializable
Using this code:
class ContactDetail(APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
queryset = Contact.objects.all()
renderer_classes = (BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer,)
"""
Retrieve, update or delete a contact instance.
"""
def get_object(self, pk):
try:
return Contact.objects.get(pk=pk)
except Contact.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
contact = self.get_object(pk)
serializer = ContactSerializer(contact)
if format == 'json' or format == "api":
return Response(serializer.data)
else:
return Response({'contact': contact, 'serializer':serializer}, template_name="contact/contact_detail.html")
But GET /contacts/1.json , 1.api, or 1.html ALL give me the correct output. So it seems that I have created an issue with the content negotiation for the default i.e. format=None
I must be missing something fundamental. I have gone through the 2 tutorials and read the Renderers docs but I am unclear on what I messed up here as far as the default. I am NOT using the DEFAULT_RENDERERS in settings.py, didn't seem to make a difference if in default or inside the actual class as shown above.
Also if anyone knows a way to use TemplateHTMLRenderer without needing to switch on format value, I'm all ears.
EDIT: IF I use
if format == 'json' or format == "api" or format == None:
return Response(serializer.data)
else:
return Response({'contact': contact, 'serializer':serializer},
Then I am shown the browsable API by default. Unfortunately, what I want is the Template HTML view by default, which is set to show forms for end users. I would like to keep the .api format for developers.
TL; DR: Check the order of your renderers - they are tried in order of declaration until a content negotiation match or an error occurs.
Changing the line
renderer_classes = (BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer, )
to
renderer_classes = (TemplateHTMLRenderer, BrowsableAPIRenderer, JSONRenderer, )
Worked for me. I believe the reason is because the content negotiator starts at the first element in the renderer classes tuple when trying to find a renderer. When I have format==None, I'm thinking there is nothing else for DRF to go on, so it assumes I mean the "default" which seems to be the first in the tuple.
EDIT: So, as pointed out by #Ross in his answer, there is also a global setting in the settings.py for the project. If I remove my class level renderer_classes declaration and instead use this in settings.py
# ERROR
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.BrowsableAPIRenderer',
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
)
}
Then I get a (different) JSON error. However, as long as
'rest_framework.renderers.BrowsableAPIRenderer',
is not listed first, for example:
# SUCCESS, even though JSON renderer is checked first
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
)
So if we hit BrowsableAPIRenderer before we try TemplateHTMLRenderer then we get an error - whether or not we are relying on renderer_classes or DEFAULT_RENDERER_CLASSES. I imagine it passes through JSONRenderer gracefully but for whatever reason BrowsableAPIRenderer raises an exception.
So I have simplified my view code after analyzing this...
def get(self, request, pk, format=None):
contact = self.get_object(pk)
serializer = ContactSerializer(contact)
if format == None:
return Response({'contact': contact, 'serializer':serializer}, template_name="contact/contact_detail.html")
else:
return Response(serializer.data)
..which better reflects what I was originally trying to do anyway.
When I look at the source code, the priority seems to be the order of the renderers specified in the DEFAULT_RENDERER_CLASSES parameter in settings.py:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.TemplateHTMLRenderer',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.TemplateHTMLRenderer',
)
}
So, if you specify a bunch of renderer classes, the first renderer that is valid will be selected based on if it is valid for the request given the .json/.api/.html extension and the Accept: header (not content-type, as I said in the comment on your question).

Include JSON from Django Rest Framework in an HTML template

I am trying to do something very simple but haven't found how to do it yet.
I have a model and an endpoint returning a JSON array reprenseting the instances of this model with Django Rest Framework. I want to include the JSON in an HTML template (for SEO and for fast initial data loading). Something like
<script>
var data = {% json_from_django_rest_framework "mymodel" %};
</script>
Is there an easy way to do this? Should I just go a different way?
Another way of doing this, which gets around rendering the view.
In your views.py;
class FooDetailView(DetailView):
model = Foo
template_name = 'foo/detail.html'
def get_context_data(self, **kwargs):
bars = []
for bar in self.object.bars.all():
bars.append(BarSerializer(bar).data)
kwargs['bars'] = JSONRenderer().render(bars)
return super(FooDetailView, self).get_context_data(**kwargs)
And in your template;
<script>
var bars = {{ bars|safe }};
</script>
It should really go without saying that you should pay attention to potential performance issues with this approach, ie.. perhaps it's best to paginate bars.
As discussed in the comments, here is an example of how to reuse the result from your api endpoint in a normal Django view by using Django's resolve function.
views.py
import json
from django.core.urlresolvers import resolve
from django.views.generic.base import View
class FooView(View):
def get(self, request):
# optional stuff in your view...
##
# Resolving another Django view programmatically
##
rev = '/path/to/api/endpoint/' # or use reverse()
view, vargs, vkwargs = resolve(rev)
vkwargs['request'] = request
res = view(*vargs, **vkwargs)
c = Context({
'data': json.dumps(res.data)
})
# Now the JSON serialized result from the API endpoint
# will be available in the template variable data.
return render(request, 'my-app/my-template.html', c)
my-template.html
<script>
var data = {{ data }};
</script>
Note 1: Instead of hardcoding the path in rev = '/path/to/api/endpoint/' it is better to reverse() the url, but I left it out to remove that as a source for errors. If you are going this direction, here is a list of the default url names provided by DRF
Note 2: The snippet would benefit from exception handling, like making sure that res returned 200, has the data property, etc.

Django Rest Framework: Retrieving object count from a model

Does anyone know how can I successfully retrieve the object count of a model, in JSON format, and how I need to configure my routing? I'm trying to achieve this using a APIView and returning a Response formatted by JSONRenderer.
UPDATE:
#api_view(['GET'])
#renderer_classes((JSONRenderer, JSONPRenderer))
def InfluenciasCountView(request, format=None):
influencia_count = Influencia.objects.count()
content = {'influencia_count': influencia_count}
return Response(content)
Here's the route I'm using:
url(r'^influencias/count/$', views.InfluenciasCountView, name='influencias-count')
Check out this snippet of code (the second one). If this does not suit your need, please add some of your code (for better understanding).
UPDATE
For routing, DRF offers a default router for each view. This means that you can have the following configuration in your urls.py: (using the example from the previous link)
url(r'^users/count/$', views. UserCountView.as_view(), name='users-count')
Then, when you access the URL your_base_url/users/count/ you will see something like {'user_count': 10}.
UPDATE 2
The entire code should look like this:
class UserCountView(APIView):
"""
A view that returns the count of active users.
"""
renderer_classes = (JSONRenderer, )
def get(self, request, format=None):
user_count = User.objects.count()
content = {'user_count': user_count}
return Response(content)
I am using routers from REST Framework to build my URLs. I tried above code but doesn't get it working. One of the problems is I cannot get /count/ into the router endpoints.
I checked DRF document (3.8.2) and found that there is a (new?) #action decorator (I was using 3.7.7 and it doesn't have it). So, here is my full solutions:
Upgrade DRF to 3.8.2 (or above) in requirements.txt (or PipFile if you using that).
Add a new action count method to the ModelViewSet
Update get_permissions to include the newly added action count
Here is my views.py:
from rest_framework.decorators import action
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows recommend to be viewed or edited.
"""
model = Post
queryset = Post.objects.filter(is_active=True)
serializer_class = serializers.PostSerializer
filter_backends = (filters.SearchFilter, DjangoFilterBackend,)
search_fields = ('title', 'body',)
filter_fields = ('status', 'type')
def get_permissions(self):
if self.action in ('list', 'retrieve', 'create', 'count'):
return (AllowAny()),
if self.action in ('update', 'partial_update'):
return (IsAdminUser()),
return (IsAdminUser()),
#action(detail=False)
def count(self, request):
queryset = self.filter_queryset(self.get_queryset())
count = queryset.count()
content = {'count': count}
return Response(content)
To query count of posts: /api/posts/count/?format=json
To query count of published: /api/posts/count/?format=json&status=published
One of the important thing here is to use the queryset from filter_queryset(...), rather than Post.objects.all().
UPDATE
Since count is common, I created a mixin for that.
from rest_framework.decorators import action
from rest_framework.response import Response
class CountModelMixin(object):
"""
Count a queryset.
"""
#action(detail=False)
def count(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
content = {'count': queryset.count()}
return Response(content)
To use it, just add CountModelMixin to your ModelViewSet (also support nested ModelViewSet).
class PostViewSet(viewsets.ModelViewSet, CountModelMixin):
If you use permissions, also add 'count' to the list of granted action.