Cache staleness, even though I am not using a cache - django

I have uploaded my Django site on a development server at Digital Ocean and started serving it using apache2 and mod_wsgi. I am also using apache2 to serve static content. It's a fresh installation of Debian on the server, and have done minimal changes.
I am using Django Haystack and Solr to support full-text search. I've checked that both the database and Solr are kept up to date.
Now, if the url I use to retrieve a page contains a query string (it can be as small a ?asd, where asd is not a valid param) everything comes out fine.
If however it does not contain such a query string, I get a version of the page that can be several hours (days?) old, even containing items I've deleted from the database.
The issue is resolved as soon as I restart apache2.
Doing apache2ctl -M | grep cache returns nothing, and settings.py does not reference caching either. Anyone have an idea what could be the cause of this?
Update: my view is a standard search view:
class SearchView(SearchView):
form_class = ArticleSearchForm
template_name = 'articles/index.html'
paginate_by = 5
def get_queryset(self):
queryset = super(SearchView, self).get_queryset()
return queryset
def get_context_data(self, *args, **kwargs):
context = super(SearchView, self).get_context_data(*args, **kwargs)
context['ctx'] = context
context['articles'] = map(lambda o: o.object, context['object_list'])
return context
And the associated search form:
class ArticleSearchForm(SearchForm):
start_date = forms.DateField(required=False, widget=forms.DateInput(attrs={ 'id': 'start_date', 'class': 'form-control' }))
end_date = forms.DateField(required=False, widget=forms.DateInput(attrs={ 'id': 'end_date', 'class': 'form-control'}))
def __init__(self, *args, **kwargs):
super(ArticleSearchForm, self).__init__(*args, **kwargs)
self.fields['q'].widget.attrs['class'] = 'form-control'
def search(self):
sqs = super(ArticleSearchForm, self).search()
if not self.is_valid():
return self.no_query_found()
if not self.cleaned_data.get('q'):
sqs = self.searchqueryset.all()
if self.cleaned_data['start_date']:
sqs = sqs.filter(pub_date__gte=self.cleaned_data['start_date'])
if self.cleaned_data['end_date']:
sqs = sqs.filter(pub_date__lte=self.cleaned_data['end_date']+datetime.timedelta(days=1))
return sqs
But considering the page I get is inconsistent with what's in the database, I don't think my code gets called at all.
Update 2: It's weirder than this. Sometimes upon hitting refresh on my browser I get correct results. Hitting refresh again a couple of times I get incorrect results again. I tried wgetting the page on the server (via localhost) and I get incorrect results.

Related

Django Azure rest framework call 500 server error

I have an django app that is running fine locally, but deployed to azure app service I am getting a 500 error when when it requests data. The app is being deployed in a docker container on an azure app service:
URLs.py
path('primaryexams/', TemplateView.as_view(template_name='xxxDB/primaryexams.html'), name='primaryExams'),
path('primaryexamsdata/', views.PrimaryExamsView.as_view(), name='primaryexam_data'),
views.py
class PrimaryExamsView(generics.ListAPIView):
serializer_class = PrimaryExamSerializer
template_name='xxxDB/primaryexams.html'
def get_queryset(self):
return xxxPrimaryExamData.objects.all()
def filter_for_datatable(self, queryset):
# filtering
search_query = self.request.query_params.get('search[value]')
if search_query:
lookups = Q(xxxid__first_name__icontains=search_query)|Q(xxxid__last_name__icontains=search_query)|Q(xxxid__xx_id__icontains=search_query)
queryset = xxxPrimaryExamData.objects.filter(lookups)
return queryset
def list(self, request, *args, **kwargs):
draw = request.query_params.get('draw')
queryset = self.filter_queryset(self.get_queryset())
recordsTotal = queryset.count()
filtered_queryset = self.filter_for_datatable(queryset)
try:
start = int(request.query_params.get('start'))
except (ValueError, TypeError):
start = 0
try:
length = int(request.query_params.get('length'))
except (ValueError, TypeError):
length = 25
end = length + start
serializer = self.get_serializer(filtered_queryset[start:end], many=True)
response = {
'draw': draw,
'recordsTotal': recordsTotal,
'recordsFiltered': filtered_queryset.count(),
'data': serializer.data,
}
return Response(response)
serializers.py
class PrimaryExamSerializer(serializers.ModelSerializer):
xxx_id = serializers.ReadOnlyField(source='xxxid.xxx_id')
last_name = serializers.ReadOnlyField(source='xxxid.last_name')
first_name = serializers.ReadOnlyField(source='xxxid.first_name')
program_institution = serializers.ReadOnlyField(source='program_institution.institution_id')
program_institution_name = serializers.ReadOnlyField(source='program_institution.institution_name')
test_center_institution = serializers.ReadOnlyField(source='test_center_institution.institution_id', default='none')
class Meta:
model = AbnsPrimaryExamData
fields = (
'id','xxx_id','last_name','first_name','medical_school','program_institution','program_institution_name','graduation_year','test_center_institution'
)
When I try to load the data I get an ajax error, and when I look at the request its getting a 500 server error:
https://xxxinternal.azurewebsites.net/xxxDB/primaryexamsdata/?draw=1&columns%5B0%5D%...blah...blahh
I have other views set up this same way that work just fine, but for what ever reason this view throws this error and displays no data. When I copy the url and paste it into my local version it displays the response just fine.
it turns out my problem was with gunicorn inside the docker. Running the docker locally also failed, but it gave me more information: Bad Request
Request Line is too large (6060 > 4094)
setting --limit-request-line 8000 fixed the issue.

Django .save() doesn't take effect

I would like to understand why I need to rerun the Django server in order to watch the effect of .save() method.
When I upload a new document, I use code that lets me see if the document is recent or not. For example, I set the expiration time to 15 seconds after the upload part (15 seconds is for testing purposes, it will be 1 week in production).
I have this class :
class DocumentListView(CreateView):
""" Render the home page """
template_name = 'documentList.html'
form_class = DocumentForm
def get_context_data(self, **kwargs):
now = timezone.now()
for document in Document.objects.all():
expiration_delay = document.creation_date + timedelta(seconds=15)
if now > expiration_delay:
document.new_document = False
document.save()
kwargs['document_list'] = Document.objects.all().order_by('publication__category__name')
return super(HomeView, self).get_context_data(**kwargs)
It works fine, but after 15 seconds, when I want to refresh my page, the flag is still there. It disappears only when I rerun my Django server.

Can't get dynamic python ModelChoiceField to work

I'm new to python and trying to understand how to get a dynamic ModelChoiceField to work. It works fine when I select an object with all but I'm trying to get the dropdown to reflect a user's attribute. Here is my code:
Forms.py
class ViewByMake(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Make.objects.none())
def __init__(self, user, *args, **kwargs):
user = kwargs.pop('user')
super(ViewByMake, self).__init__(*args, **kwargs)
qs = Make.objects.filter(user=user)
self.fields['dropdown'].queryset = qs
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
Views.py
def view_bymake(request):
form = ViewByMake(request.POST or None, user=request.user)
if request.method == 'POST':
if form.is_valid():
make = form.cleaned_data['dropdown']
return HttpResponseRedirect(make.get_absolute_url1())
return render(request,'make/view_make.html',{'form':form})
This code works fine if I remove all user= references but then only returns the full make objects list which is not what I want. I found a very similar question on StackOverflow, but when I duplicated the code identically, it still doesn't work and it is giving me the following error:
init() got multiple values for argument 'user'
I searched the end of the internet on this topic. I'm open to other ideas if I'm approaching this poorly. I'm trying to basically get a filtered list based on criteria associated with a user's profile. I definitely need the drop down field to be specific to a user based on a profile setting. Thanks for your help in advance. I'm running django 1.11.2 and Python 3.6.1.
This is the updated model which need to include the user attribute which I didn't realize that I had to specify:
class Make(models.Model):
name = models.CharField(max_length=264,unique=True)
user = models.ForeignKey(User,null=True,on_delete=models.CASCADE)
Try with request, send request from form and get request in init method of form
views.py
def view_bymake(request):
form = ViewByMake(request.POST or None, request=request)
forms.py
def __init__(self, user, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(ViewByMake, self).__init__(*args, **kwargs)
qs = Make.objects.filter(user=self.request.user)
self.fields['dropdown'].queryset = qs
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
The answer to my original question, how do I get user=user to work consists of making sure that your form, view, and model all reference user. I originally had the user reference in the view and the form correct, but I neglected to make sure user= was specified on the model I was referencing. I thought it was built in, but turns out you have to specifically reference it on your model. I'm new at this so it was a learning experience. On to the next challenge!

Django: limit models.ForeignKey results

I have an order model:
class Order(models.Model):
profile = models.ForeignKey(Profile, null=True, blank=True)
Which returns all possible profiles for an order, which is not necessary and slowing down the loading of the admin order page.
I want the profile returned to simply be the profile of the user who placed the order. I've tried changing it to:
class Order(models.Model):
profile = models.ForeignKey(Profile, null=True, blank=True, limit_choices_to={'order': 99999})
which returns the correct profile for order number 99999, but how can I get this dynamically. The Order model is not aware of the 'self', but the order number is contained in the URL.
What is the best way to do this?
If you are using the Django Admin, you can override the method formfield_for_foreignkey on your ModelAdmin class to modify the queryset for the profile field dinamically based on a GET parameter for instance (as you have access to request inside the method.
Look at this example from the docs (adapted for your case):
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "profile":
# You can get the order number from the GET parameter:
# order = request.GET.get('order')
# or a fixed number:
order = '12345'
kwargs["queryset"] = Profile.objects.filter(order=order)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Reference in the docs: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I took another look at this and it seems to be working, although it seems like a bit of hack! The problem was that the order number doesn't seem to exist in the request so I am parsing the URL requested. I put this in my order admin:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "profile":
try:
order = int(filter(str.isdigit, str(request.path_info)))
except:
order = request.GET.get('order')
kwargs["queryset"] = Profile.objects.filter(order=order)
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
The line that filters the url string for an integer works for the change order page of the admin, but didn't work for the order page overview, so I added the try/except. Any suggestions/improvements welcome!
I assume from the context you're referring to the display on the Django Admin page. If you set
raw_id_fields = {'my_foreign_key'}
you will get a number, text description of related model (from str) and a nice popup box to open an instance of the admin page for your related models.
You could alternatively use
list_select_related = True
to get the same behaivour you have now but with a couple of order of magnitudes lower number of queries.

How to preserve filter selection after save in Django Admin

First, I did look at this question, but its over a year old. Surely now there is a good way in Django 1.1.1 to carry filter selection forward after a user clicks the save button in the Admin.
In a table with thousands of records, filtering is essential. And if a user makes several filter choices that effort shouldn't have to be repeated.
The answer is still the same: out of the box, Django doesn't support this behavior. There are a couple of tickets in the issue tracker with patches: #3777, #6903. The middleware class in this comment works without modifying Django code.
This feature has been added to Django as part of the 1.6 release and is enabled now by default. It is described in the release notes:
ModelAdmin now preserves filters on the list view after creating,
editing or deleting an object. It’s possible to restore the previous
behavior of clearing filters by setting the preserve_filters attribute
to False.
another way is to use this snippet http://djangosnippets.org/snippets/2531/
Class Modeladmin_perso(admin.ModelAdmin):
def add_view(self, request, *args, **kwargs):
result = super(Modeladmin_perso, self).add_view(request, *args, **kwargs )
# Look at the referer for a query string '^.*\?.*$'
ref = request.META.get('HTTP_REFERER', '')
if ref.find('?') != -1:
# We've got a query string, set the session value
request.session['filtered'] = ref
if request.POST.has_key('_save'):
"""
We only kick into action if we've saved and if
there is a session key of 'filtered', then we
delete the key.
"""
try:
if request.session['filtered'] is not None:
result['Location'] = request.session['filtered']
request.session['filtered'] = None
except:
pass
return result
"""
Used to redirect users back to their filtered list of locations if there were any
"""
def change_view(self, request, object_id, extra_context={}):
"""
save the referer of the page to return to the filtered
change_list after saving the page
"""
result = super(Modeladmin_perso, self).change_view(request, object_id, extra_context )
# Look at the referer for a query string '^.*\?.*$'
ref = request.META.get('HTTP_REFERER', '')
if ref.find('?') != -1:
# We've got a query string, set the session value
request.session['filtered'] = ref
if request.POST.has_key('_save'):
"""
We only kick into action if we've saved and if
there is a session key of 'filtered', then we
delete the key.
"""
try:
if request.session['filtered'] is not None:
result['Location'] = request.session['filtered']
request.session['filtered'] = None
except:
pass
return result
the good thing is you don't have to hack anything.
This feature has been a request to the Django project for a long time (the ticket was opened 5 years ago).
Fortunately this annoying behavior was fixed in trunk. Expect it to be included in Django 1.6.
Here's what I did inside render_change_form to generate a back button with preserved_filters.
def generate_back_url(self, request):
opts = self.model._meta
post_url = reverse(
"admin:%s_%s_changelist" % (opts.app_label, opts.model_name),
current_app=self.admin_site.name,
)
preserved_filters = self.get_preserved_filters(request)
return add_preserved_filters(
{"preserved_filters": preserved_filters, "opts": opts}, post_url
)