Django Application zum loggen - django

Let's say I have Two Models:
class Thinclient(models.Model):
hostname = models.TextField(_('hostname'),unique=True, \
editable=False)
logs = models.ManyToManyField(Log, blank=True, null=True)
class Log(models.Model):
logname = models.TextField(editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
As you can see I have thin clients. Now If one of those thin clients boots it is supposed to send a POST request to my app with the thin client name and a logname (i.e. "booting ...").
Now my view will handle all the work and that's where I have my problem. It currently looks like this:
def log(request):
if request.method == 'POST':
form = ThinclientForm(request.POST)
if form.is_valid():
message = form.cleaned_data['logname']
Log.objects.get_or_create(logname=message)
return HttpResponse(content="", mimetype=None, status=200)
else:
return HttpResponse(content="Unsuccessful", mimetype=None,
status=400)
return render_to_response('thin/status', {
'form': form, })
However that won't wok because I have to assign a message to one specific thin client. I suppoose I have to write my own form with hostname, logname and that is where i have my problem how can I save my models in a way that the message is assigned to a thin?
I hope I could explain what I need to know, if not tell me. And Thanks for any help in this

Assuming your Thinclient name is also included in the POST, you can just get it from there and use it to look up the actual object, then assign that Log message to it.
log = Log.objects.get_or_create(logname=message)
client = Thinclient.objects.get(hostname=request.POST['clientname']
client.logs.add(log)
(One note: you shouldn't use TextFields for things like client names - these are stored as BLOB/TEXT objects in the database, which is far less efficient than normal VARCHARs. Use CharField instead.)

There are several problems here. The first is in the design of your models. The link from Log to ThinClient should be a ForeignKey from Log to ThinClient
class Thinclient(models.Model):
hostname = models.TextField(_('hostname'),unique=True, \
editable=False)
class Log(models.Model):
thin_client = models.ForeignKey(Thinclient)
logname = models.TextField(editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
Do you even need a form here? Aren't your thin clients just doing a post to this url? Are they really doing a GET to get the form? The benefit of forms is the ability to turn them into HTML and data validation. I don't think you need either of those here.
The host name of the remote client is stored in the REMOTE_HOST request header so you can use this to pull it out.
def log(request):
thin_client, _ = Thinclient.get_or_create(hostname=request.META["REMOTE_HOST"])
Log(thin_client=thin_client, logname=request.POST["logname"]).save()
return HttpResponse(content="OK", mimetype="text/plain")
As a side note you should always return some content. Some proxies do not like zero byte responses. You should also always specify a mimetype, even if it's the default of text/html.

Related

How to paginate different objects of the same endpoint in Django Rest Framework?

Lest supose that I have a model named Collection. I can create a collection, this collection have two important fields: share_with_company, share_list.
class Collection(models.Model):
name = models.CharField(max_length=200, blank=False, null=False)
share_with_company = models.BooleanField(default=False)
share_list = ArrayField(models.CharField(max_length=15), null=True, blank=True)
owner = models.CharField(max_length=200, blank=False, null=False)
currently I have an endpoint: /collections
and this endpoint need to return something like it:
{
shared_with_company:{collections: ..., count: 5}
shared_list:{collections: ..., count: 12}
my_collections:{collections: ..., count: 20} //dont shared with anyone, created by the current user
}
but, in the frontend the user want to paginate just my_collections or just shared_list or shared_with_company. I like performance, so should I create a specifics endpoints to each type of collections? But, everytime the user load the collections page will show 12 (max per page) collections of each (my_collections, shared etc.), and then he will be able to paginate it. I don't know if this is the best way to do it, I think a lot of users send 3 requests everytime the page is loaded.
Another approach: use an endpoint to load the initial page, and this enpoint will make one request to the first page and the paginations will be made with differents endpoints.
I really don't know if there is a better approach or something like that.
The usual approach will be creating three different endpoints. It's okay when user sends 3 requests to the api, actually it may be even better as your frontend can show data more dynamically. Also, you don't need to return all the data every time, just a list user requested.
If you use ModelViewSet, since it's one model for three endpoints, you can just add new actions with #action decorator and override get_queryset. So you will have endpoints like these - collections/shared_with_company, collections/shared_list and collections/my_collections
And in your get_queryset you can determine what queryset you would like to return, for example:
def get_queryset(self):
queryset = super().get_queryset()
if self.action == 'list':
return queryset
elif self.action == 'shared_with_company':
return queryset.filter(share_with_company=True)

Django app goes haywire when deployed. Race conditions?

I wrote a django app for quizzing, and it checks the user's answers and updates scores as soon as the user submits an answer. Here is the corresponding view to do this -
current_question_key = 0 #This is a global variable.
def check_answer(request):
current_user = request.user
current_team = Team.objects.get(user = current_user)
current_score = current_team.score
if request.method == "POST":
answer = request.POST.get('answer')
question = Question.objects.get(id = current_question_key)
if answer == question.answer:
if question in current_team.questions_answered.all(): #This is required to prevent the score from increasing if the somebody submits a correct answer to the same question more than once
pass
else:
current_team.score = current_score + question.score_increment
current_team.questions_answered.add(question)
current_team.save()
else:
# This is required to prevent the score from decreasing if someone has answered it correctly earlier
if question in current_team.questions_answered.all():
pass
else :
current_team.score = current_score - question.score_increment//negative_marking_factor
current_team.save()
return HttpResponse(status=204) #This means that the server has successfully processed the request and is not going to return any data.
else:
return HttpResponse("Error404")
The value of current_question_key is changed from the view used to send the question to the front end -
def game(request):
if request.method == "POST":
key = request.POST.get('questionKey')
global current_question_key
current_question_key = key
question = Question.objects.get(id = key)
question_text = question.question_text
data = {
'question_text':question_text
}
return JsonResponse(data)
else:
current_user = request.user
current_team = Team.objects.get(user = current_user)
score = current_team.score
name = current_user.username
return render(request, 'Base/main.html', {'teamname':name, 'score':score})
When tested on django's development server, this worked perfectly fine even when around 10 people were using it simultaneously. But, as soon as I tried to serve it with nginx (hosted on my laptop, with 5 simultaneous users), the app went totally haywire and even correct answers were evaluated as wrong.
I tried apache too and had the same problem with it. Almost all requests were handled incorrectly. Could this be related to race conditions? What exactly might be going on here?
You cannot use a global variable like this in Django. A Django application usually runs in multiple server processes which do not share memory. Calling the game view would only set the global variable current_question_key in one of the processes. All other processes would still have old values. As a request can be served by any process, you get more or less random results.
The Django development server uses multi-threading instead of multi-processing. Threads, as opposed to processes, share the same memory, so all request see the same value for current_question_key.
You have to store current_question_key for each user in a way that is accessible to all processes. The most obvious solution would be to store this information in the user's session:
request.session['current_question_key'] = ...
Alternatively, you could store it in the database, e.g. with ForeignKey in a customer user model, or if you want to keep track of games any in a separate table like this:
from django.contrib.auth import get_user_model
from django.db import models
class Game(model.Model)
user = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
question = models.ForeignKey(Question, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
You can then get the current game for a user by sorting by creation date:
Game.objects.filter(user=request.user).order_by('-created_at').first()
Depending on how often the current question changes, you could also consider using a key-value like Redis, although that complicates things a bit.

What is the right way to hard-code some methods and fields of particular instances of a model in Django?

I have a site that pulls feeds in from various sites. Most of these are via RSS, but a few are via API, like HackerNews. I have a ListView that shows all of my feeds, so I want to keep all of these the same type of model.
What I'm looking to do is this (does not work):
class Feed(models.Model):
name = models.CharField(max_length=75)
rss = models.URLField(blank=True, null=True)
def get_feed_data(self):
# return data based on self.rss
class HackerNewsFeed(Feed):
name = "Hacker News" # Doesn't work
def get_feed_data(self):
# Return data based on Hacker News API
The answers to this question seem to say that I should simply use the Feed model with custom methods based on the name...then I would manually input "HackerNews" into the database and this would automatically trigger my API code. It feels a little hacky.
What is the best way to achieve what I'm looking for?
Since Feed is a models.Model you can only assign model fields as class parameters. Assigning name = "Hacker News" won't work on models. Since it doesn't look like the shape of the data needs to change depending on the feed type, then just leave your model as-is, and implement your feed fetching based on the self.name attribute. Something like this:
# assumes a file called `feedfetcher.py` in the same
# directory as your `models.py` file
from . import feedfetcher
class Feed(models.Model):
name = models.CharField(max_length=75)
rss = models.URLField(blank=True, null=True)
def get_feed_data(self):
return feedfetcher.fetch(self.name)
# feedfetcher.py
def fetch(name):
if name == 'Hacker News':
# get hacker news feed
elif name == 'Reddit':
# get reddit feed
else:
# we have no fetcher for this
# kind of feed yet.
raise ValueError('No fetcher for {}'.format(name))
If you add too many feeds this will quickly become unruly. I would probably split RSS feeds into a common method or class to make handling of those automatic. Add the following attribute to your model:
feed_type = models.CharField(choices=(('RSS', 'RSS'), ('API', 'API'))
And then you could check what the feed type is within get_feed_data and if it's RSS, hand off to a specific RSS feed handler. I'm assuming each API will be different though, so you can keep the fetch method above to figure out and call each individual API.
I suggest inserting the fixed Feeds using fixtures and then make get_feed_data conditional based on name.
def get_feed_data(self):
if self.name == "Hacker News":
# Return data based on Hacker News API
else:
# return data based on self.rss

Django - Lazy results with a context processor

I am working on a django project that requires much of the common page data be dynamic. Things that appear on every page, such as the telephone number, address, primary contact email etc all need to be editable via the admin panel, so storing them in the settings.py file isn't going to work.
To get around this, I created a custom context processor which acts as an abstract reference to my "Live Settings" model. The model looks like this:
class LiveSetting(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=255, blank=False, null=False)
description = models.TextField(blank=True, null=True)
key = models.CharField(max_length=100, blank=False, null=False)
value = models.CharField(max_length=255, blank=True)
And the context processor like so:
from livesettings.models import LiveSetting
class LiveSettingsProcessor(object):
def __getattr__(self, request):
val = LiveSetting.objects.get(request)
setattr(self, val.key, val.value)
return val.value
l = LiveSettingsProcessor()
def livesetting_processors(request):
return {'settings':l}
It works really nicely, and in my template I can use {{ settings.primary_email }} and get the corresponding value from the database.
The problem with the above code is it handles each live setting request individually and will hit the database each time my {{ settings.*}} tag is used in a template.
Does anyone have any idea if I could make this process lazy, so rather than retrieve the value and return it, it instead updates a QuerySet then returns the results in one hit just before the page is rendered?
You are trying to invent something complex and these is no reason for that. Something as simple as this will work fork you good enough:
def livesetting_processors(request):
settings = LiveSetting.objects.get(request)
return {'settings':settings}
EDIT:
This is how you will solve your problem in current implementation:
class LiveSettingsProcessor(object):
def __getattr__(self, request):
val = getattr(self, '_settings', LiveSetting.objects.get(request))
setattr(self, val.key, val.value)
return val.value
#Hanpan, I've updated my answer to show how you can to solve your problem, but what I want to say is that things you are trying to achieve does not give any practical win, however it increase complexity ant it takes your time. It might also be harder to setup cache on all of this later. And with caching enabled this will not give any improvements in performance at all.
I don't know if you heard this before: premature optimization is the root of evil. I think this thread on SO is useful to read: https://stackoverflow.com/questions/211414/is-premature-optimization-really-the-root-of-all-evil
Maybe you could try Django's caching?
In particular, you may want to check out the low-level caching feature. It seems like it would be a lot less work than what you plan on.

Track the number of "page views" or "hits" of an object?

I am sure that someone has a pluggable app (or tutorial) out there that approximates this, but I have having trouble finding it: I want to be able to track the number of "views" a particular object has (just like a question here on stackoverflow has a "view count").
If the user isn't logged in, I wouldn't mind attempting to place a cookie (or log an IP) so they can't inadvertently run up the view count by refreshing the page; and if a user is logged in, only allow them one "view" across sessions/browsers/IP addresses. I don't think I need it any fancier than that.
I figure the best way to do this is with Middleware that is decoupled from the various models I want to track and using an F expression (of sorts) -- other questions on StackOverflow have alluded to this (1), (2), (3).
But I wonder if this code exists out in the wild already -- because I am not the savviest coder and I'm sure someone could do it better. Smile.
Have you seen it?
I am not sure if it's in the best taste to answer my own question but, after a bit of work, I put together an app that solves the problems in earnest: django-hitcount.
You can read about how to use it at the documentation page.
The ideas for django-hitcount came came from both of my two original answers (Teebes -and- vikingosegundo), which really got me started thinking about the whole thing.
This is my first attempt at sharing a pluggable app with the community and hope someone else finds it useful. Thanks!
You should use the django built-in session framework, it already does a lot of this for you. I implemented this in the following way with a Q&A app where I wanted to track views:
in models.py:
class QuestionView(models.Model):
question = models.ForeignKey(Question, related_name='questionviews', on_delete=models.CASCADE)
ip = models.CharField(max_length=40)
session = models.CharField(max_length=40)
created = models.DateTimeField(default=datetime.datetime.now())
in views.py:
def record_view(request, question_id):
question = get_object_or_404(Question, pk=question_id)
if not QuestionView.objects.filter(
question=question,
session=request.session.session_key):
view = QuestionView(question=question,
ip=request.META['REMOTE_ADDR'],
created=datetime.datetime.now(),
session=request.session.session_key)
view.save()
return HttpResponse(u"%s" % QuestionView.objects.filter(question=question).count())
Vikingosegundo is probably right though that using content-type is probably the more reusable solution but definitely don't reinvent the wheel in terms of tracking sessions, Django already does that!
Last thing, you should probably have the view that records the hit be either called via Ajax or a css link so that search engines don't rev up your counts.
Hope that helps!
You could create a generic Hit model
class Hit(models.Model):
date = models.DateTimeField(auto_now=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
In your view.py you write this function:
def render_to_response_hit_count(request,template_path,keys,response):
for key in keys:
for i in response[key]:
Hit(content_object=i).save()
return render_to_response(template_path, response)
and the views that you are interested in return
return render_to_response_hit_count(request, 'map/list.html',['list',],
{
'list': l,
})
This approach gives you the power, not only to count the hit, but to filter the hit-history by time, contenttype and so on...
As the hit-table might be growing fast, you should think about a deletion strategy.
I know this question is an old one and also thornomad has put an app to solve the problem and inspire me with me solution. I would like to share this solution since I didn't find much information about this topic and it may help someone else.
My approach is to make a generic model can be used with any view based on the view path (url).
models.py
class UrlHit(models.Model):
url = models.URLField()
hits = models.PositiveIntegerField(default=0)
def __str__(self):
return str(self.url)
def increase(self):
self.hits += 1
self.save()
class HitCount(models.Model):
url_hit = models.ForeignKey(UrlHit, editable=False, on_delete=models.CASCADE)
ip = models.CharField(max_length=40)
session = models.CharField(max_length=40)
date = models.DateTimeField(auto_now=True)
views.py
def get_client_ip(request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def hit_count(request):
if not request.session.session_key:
request.session.save()
s_key = request.session.session_key
ip = get_client_ip(request)
url, url_created = UrlHit.objects.get_or_create(url=request.path)
if url_created:
track, created = HitCount.objects.get_or_create(url_hit=url, ip=ip, session=s_key)
if created:
url.increase()
request.session[ip] = ip
request.session[request.path] = request.path
else:
if ip and request.path not in request.session:
track, created = HitCount.objects.get_or_create(url_hit=url, ip=ip, session=s_key)
if created:
url.increase()
request.session[ip] = ip
request.session[request.path] = request.path
return url.hits
I did this by creating a model PageViews and making a column "Hits" in it. Every time when Homepage url is hit. I increment the first and only row of column Hit and render it to the template. Here how it looks.
Views.py
def Home(request):
if(PageView.objects.count()<=0):
x=PageView.objects.create()
x.save()
else:
x=PageView.objects.all()[0]
x.hits=x.hits+1
x.save()
context={'page':x.hits}
return render(request,'home.html',context=context)
Models.py
class PageView(models.Model):
hits=models.IntegerField(default=0)
I did it using cookies. Don't know if it's a good idea to do that or not. The following code looks for an already set cookie first if it exists it increases the total_view counter if it is not there the it increases both total_views and unique_views. Both total_views and unique_views are a field of a Django model.
def view(request):
...
cookie_state = request.COOKIES.get('viewed_post_%s' % post_name_slug)
response = render_to_response('community/post.html',context_instance=RequestContext(request, context_dict))
if cookie_state:
Post.objects.filter(id=post.id).update(total_views=F('total_views') + 1)
else:
Post.objects.filter(id=post.id).update(unique_views=F('unique_views') + 1)
Post.objects.filter(id=post.id).update(total_views=F('total_views') + 1)
response.set_cookie('viewed_post_%s' % post_name_slug , True, max_age=2678400)
return response