Compare commits

...
16 Commits
647 changed files with 26944 additions and 63502 deletions

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# The first instruction is what image we want to base our container on
# We Use an official Python runtime as a parent image
#FROM python:3.6
# FROM directive instructing base image to build upon
FROM python:3.6.6
# The enviroment variable ensures that the python output is set straight
# to the terminal with out buffering it first
#ENV PYTHONUNBUFFERED 1
# App directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Install azure event hub client dependencies
#COPY p /usr/src/app/
# Bundle app source
COPY . /usr/src/app
# COPY startup script into known file location in container
COPY start.sh /start.sh
# create root directory for our project in the container
#RUN mkdir /esther_kleinhenz_ba
# EXPOSE port 8000 to allow communication to/from server
#EXPOSE 8000
RUN set -x \
&& buildDeps='curl gcc libc6-dev libsqlite3-dev libssl-dev make xz-utils zlib1g-dev'
# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt
# Set the working directory to /esther_kleinhenz_ba
#WORKDIR /esther_kleinhenz_ba
# CMD specifcies the command to execute to start the server running.
CMD ["/start.sh"]
# done!
# Copy the current directory contents into the container at /esther_kleinhenz_ba
#ADD . /esther_kleinhenz_ba/
-4
View File
@@ -1,5 +1 @@
from __future__ import absolute_import, unicode_literals
# This will make sure celery is always imported when
# Django starts so that shared_task will use this app.
from .celeryapp import app as celery_app
__all__ = ['celery_app']
+1 -41
View File
@@ -4,8 +4,7 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from .models import Post, CustomUser
from .models import ScheduledReport, ReportRecipient, ScheduledReportGroup
from .forms import ScheduledReportForm
class CustomUserInline(admin.StackedInline):
@@ -23,42 +22,3 @@ admin.site.register(User, UserAdmin)
admin.site.register(Post)
class ReportRecipientAdmin(admin.TabularInline):
model = ReportRecipient
class ScheduledReportAdmin(admin.ModelAdmin):
"""
List display for Scheduled reports in Django admin
"""
model = ScheduledReport
list_display = ('id', 'get_recipients')
inlines = [
ReportRecipientAdmin
]
form = ScheduledReportForm
def get_recipients(self, model):
recipients = model.reportrecep.all().values_list('email', flat=True)
if not recipients:
return 'No recipients added'
recipient_list = ''
for recipient in recipients:
recipient_list = recipient_list + recipient + ', '
return recipient_list[:-2]
get_recipients.short_description = 'Recipients'
get_recipients.allow_tags = True
class ScheduledReportGroupAdmin(admin.ModelAdmin):
"""
List display for ScheduledReportGroup Admin
"""
model = ScheduledReportGroup
list_display = ('get_scheduled_report_name','get_report_name')
def get_scheduled_report_name(self, model):
return model.scheduled_report.subject
def get_report_name(self, model):
return model.report.name
get_scheduled_report_name.short_description = "Scheduled Report Name"
get_report_name.short_description = "Report Name"
show_change_link = True
get_report_name.allow_tags = True
admin.site.register(ScheduledReport, ScheduledReportAdmin)
admin.site.register(ScheduledReportGroup, ScheduledReportGroupAdmin)
-13
View File
@@ -1,13 +0,0 @@
from __future__ import absolute_import
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
from django.conf import settings
app = Celery('application')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
app.config_from_object('django.conf:settings')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
+2 -27
View File
@@ -9,7 +9,6 @@ from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from datetime import datetime
from croniter import croniter
from django.forms import ModelForm, ValidationError
from .models import ScheduledReport
class PostForm(forms.ModelForm):
class Meta:
@@ -17,31 +16,7 @@ class PostForm(forms.ModelForm):
fields = ('title', 'text', 'published_date','tags')
class NewTagForm(forms.ModelForm):
m_tags = TagField()
tags = TagField()
class Meta:
model = CustomUser
fields = ['m_tags']
class ScheduledReportForm(ModelForm):
class Meta:
model = ScheduledReport
fields = ['subject', 'cron_expression']
fields = ['subject', 'cron_expression']
help_texts = {'cron_expression': 'Scheduled time is considered in UTC'}
def clean(self):
cleaned_data = super(ScheduledReportForm, self).clean()
cron_expression = cleaned_data.get("cron_expression")
try:
iter = croniter(cron_expression, datetime.now())
except:
raise ValidationError("Incorrect cron expression:\
The information you must include is (in order of appearance):\
A number (or list of numbers, or range of numbers), m, representing the minute of the hour\
A number (or list of numbers, or range of numbers), h, representing the hour of the day\
A number (or list of numbers, or range of numbers), dom, representing the day of the month\
A number (or list, or range), or name (or list of names), mon, representing the month of the year\
A number (or list, or range), or name (or list of names), dow, representing the day of the week\
The asterisks (*) in our entry tell cron that for that unit of time, the job should be run every.\
Eg. */5 * * * * cron for executing every 5 mins")
return cleaned_data
fields = ['tags']
+43 -1
View File
@@ -1,9 +1,10 @@
# Generated by Django 2.1 on 2018-08-18 15:34
# Generated by Django 2.1.2 on 2018-10-30 10:43
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import taggit.managers
class Migration(migrations.Migration):
@@ -12,6 +13,7 @@ class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('taggit', '0001_initial'),
]
operations = [
@@ -19,6 +21,8 @@ class Migration(migrations.Migration):
name='CustomUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
@@ -30,6 +34,44 @@ class Migration(migrations.Migration):
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
('published_date', models.DateTimeField(blank=True, null=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
],
),
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('report_text', models.TextField()),
],
),
migrations.CreateModel(
name='ReportRecipient',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
],
),
migrations.CreateModel(
name='ScheduledReport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.CharField(max_length=200)),
('last_run_at', models.DateTimeField(blank=True, null=True)),
('next_run_at', models.DateTimeField(blank=True, null=True)),
('cron_expression', models.CharField(max_length=200)),
],
),
migrations.CreateModel(
name='ScheduledReportGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='report', to='application.Report')),
('scheduled_report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relatedscheduledreport', to='application.ScheduledReport')),
],
),
migrations.AddField(
model_name='reportrecipient',
name='scheduled_report',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reportrecep', to='application.ScheduledReport'),
),
]
@@ -1,35 +0,0 @@
# Generated by Django 2.1 on 2018-08-18 15:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import taggit.managers
class Migration(migrations.Migration):
initial = True
dependencies = [
('taggit', '0001_initial'),
('application', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='post',
name='tags',
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
),
migrations.AddField(
model_name='customuser',
name='mytags',
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
),
migrations.AddField(
model_name='customuser',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
@@ -0,0 +1,37 @@
# Generated by Django 2.1.2 on 2018-10-30 11:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('application', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='reportrecipient',
name='scheduled_report',
),
migrations.RemoveField(
model_name='scheduledreportgroup',
name='report',
),
migrations.RemoveField(
model_name='scheduledreportgroup',
name='scheduled_report',
),
migrations.DeleteModel(
name='Report',
),
migrations.DeleteModel(
name='ReportRecipient',
),
migrations.DeleteModel(
name='ScheduledReport',
),
migrations.DeleteModel(
name='ScheduledReportGroup',
),
]
@@ -1,20 +0,0 @@
# Generated by Django 2.1 on 2018-08-30 16:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('application', '0002_auto_20180818_1734'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='user',
field=models.OneToOneField(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 2.1.2 on 2018-10-30 15:33
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('application', '0002_auto_20181030_1223'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='tags',
field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
),
]
@@ -1,20 +0,0 @@
# Generated by Django 2.1 on 2018-08-31 08:19
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('application', '0003_auto_20180830_1814'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='user',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 2.1 on 2018-10-19 14:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('application', '0004_auto_20180831_1019'),
]
operations = [
migrations.RenameField(
model_name='customuser',
old_name='mytags',
new_name='tags',
),
]
@@ -1,51 +0,0 @@
# Generated by Django 2.1 on 2018-10-21 11:47
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('application', '0005_auto_20181019_1645'),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('report_text', models.TextField()),
],
),
migrations.CreateModel(
name='ReportRecipient',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
],
),
migrations.CreateModel(
name='ScheduledReport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.CharField(max_length=200)),
('last_run_at', models.DateTimeField(blank=True, null=True)),
('next_run_at', models.DateTimeField(blank=True, null=True)),
('cron_expression', models.CharField(max_length=200)),
],
),
migrations.CreateModel(
name='ScheduledReportGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='report', to='application.Report')),
('scheduled_report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relatedscheduledreport', to='application.ScheduledReport')),
],
),
migrations.AddField(
model_name='reportrecipient',
name='scheduled_report',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reportrecep', to='application.ScheduledReport'),
),
]
+2 -38
View File
@@ -9,7 +9,7 @@ from croniter import croniter
class CustomUser(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
tags = TaggableManager()
tags = TaggableManager(blank=True)
class Post(models.Model):
author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
@@ -26,40 +26,4 @@ class Post(models.Model):
self.save()
def __str__(self):
return self.title
class Report(models.Model):
report_text = models.TextField()
class ScheduledReport(models.Model):
"""
Contains email subject and cron expression,to evaluate when the email has to be sent
"""
subject = models.CharField(max_length=200)
last_run_at = models.DateTimeField(null=True, blank=True)
next_run_at = models.DateTimeField(null=True, blank=True)
cron_expression = models.CharField(max_length=200)
def save(self, *args, **kwargs):
"""
function to evaluate "next_run_at" using the cron expression, so that it is updated once the report is sent.
"""
self.last_run_at = datetime.now()
iter = croniter(self.cron_expression, self.last_run_at)
self.next_run_at = iter.get_next(datetime)
super(ScheduledReport, self).save(*args, **kwargs)
def __unicode__(self):
return self.subject
class ScheduledReportGroup(models.Model):
"""
Many to many mapping between reports which will be sent out in a scheduled report
"""
report = models.ForeignKey(Report, related_name='report', on_delete=models.CASCADE)
scheduled_report = models.ForeignKey(ScheduledReport,
related_name='relatedscheduledreport', on_delete=models.CASCADE)
class ReportRecipient(models.Model):
"""
Stores all the recipients of the given scheduled report
"""
email = models.EmailField()
scheduled_report = models.ForeignKey(ScheduledReport, related_name='reportrecep', on_delete=models.CASCADE)
return self.title
-7
View File
@@ -1,7 +0,0 @@
from celery.task.schedules import crontab
from celery.decorators import periodic_task
from .email_service import send_emails
# this will run every minute, see http://celeryproject.org/docs/reference/celery.task.schedules.html#celery.task.schedules.crontab
@periodic_task(run_every=crontab(hour="*", minute="*", day_of_week="*"))
def trigger_emails():
send_emails()
+15 -9
View File
@@ -20,8 +20,8 @@
<img src="{% static 'images/efi.jpg' %}" width="35" height="35" alt="efi">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
@@ -34,9 +34,6 @@
<li>
<a class="nav-link" href="{% url 'post_list' %}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'student_page' %}">Studentenansicht</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog_search_list_view' %}">Suche</a>
</li>
@@ -48,9 +45,15 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'post_list' %}">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog_search_list_view' %}">Suche</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'student_page' %}">{{user.get_username}}'s Dashboard</a>
<a class="nav-link" href="{% url 'post_list' %}">{{user.get_username}}'s Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog_search_list_view' %}">Suche</a>
</li>
{% endif %}
</ul>
@@ -71,16 +74,19 @@
</a>
<ul id="login-dp" class="dropdown-menu">
<li>
<form class="form" role="form" method="post" action="{% url 'login' %}" accept-charset="UTF-8" id="login-nav">
<form class="form" role="form" method="post" action="{% url 'login' %}" accept-charset="UTF-8"
id="login-nav">
{% csrf_token %} {% if next %}
<input type="hidden" name="next" value="{{ next }}" /> {% endif %}
<div class="form-group">
<label class="sr-only" for="username">Benutzername</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Benutzername" required>
<input type="text" class="form-control" id="username" name="username"
placeholder="Benutzername" required>
</div>
<div class="form-group">
<label class="sr-only" for="password">Kennwort</label>
<input type="password" class="form-control" id="password" name="password" placeholder="Kennwort" required>
<input type="password" class="form-control" id="password" name="password"
placeholder="Kennwort" required>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block">Anmelden</button>
+11 -2
View File
@@ -1,5 +1,7 @@
{% extends 'base.html' %} {% block content %}
{% extends 'base.html' %}{% block content %}
{% load hitcount_tags %}
<div class="post">
{% if post.published_date %}
<div class="date">
{{ post.published_date }}
@@ -17,12 +19,19 @@
<h1>{{ post.title }}</h1>
<p>{{ post.text|linebreaksbr }}</p>
<p>
Tags:
{% for tag in post.tags.all %}
<a href="{% url 'blog_search_list_view' %}">{{ tag.name }}, </a>
<a href="{% url 'blog_search_list_view' %}">{{ tag.name }} </a>
{% if not forloop.last %}, {% endif %}
{% endfor %}
</p>
<p>
Autor: {{ post.author }}
</p>
{% if user.is_staff %}
<p>
{% get_hit_count for post %} Benutzer haben diesen Post bereits gelesen!
</p>
{% endif %}
</div>
{% endblock %}
+6 -16
View File
@@ -1,24 +1,14 @@
{% extends "base.html" %} {% block content %} {% load taggit_templatetags2_tags %} {% get_taglist as tags for 'application.post'%}
{% extends "base.html" %} {% block content %} {% load taggit_templatetags2_tags %}
{% get_taglist as tags for 'application.post'%}
<div id="">
<ul>
{% for tag in tags %}
<li>{{tag}}
<a class="btn btn-outline-dark" href="{% url 'tag_remove' tag.slug %}">
<span class="glyphicon glyphicon-remove">Remove</span>
</a>
</li>
{{ result }} {% endfor %}
</ul>
</div>
<div>
<form class="post-form" method="post">
{% csrf_token %} {{form.as_p}}
<button type="submit" class="save btn btn-outline-dark">Save</button>
</form>
{% if messages %}
{% for message in messages %}
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}</div>
{% endfor %}
{% endif %}
<form class="post-form" method="post">
{% csrf_token %} {{form.as_p}}
<button type="submit" class="save btn btn-outline-dark">Save</button>
</form>
{% endblock %}
+36 -30
View File
@@ -8,13 +8,16 @@ from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth import authenticate, login, logout
from django.db.models import Q
import sys
import collections
from taggit_templatetags2.views import TagCanvasListView
from django.contrib.auth.models import User
from django.contrib import messages
from post_office.models import EmailTemplate
from post_office import mail
from hitcount.models import HitCount
from hitcount.views import HitCountMixin
import logging
@@ -60,7 +63,9 @@ def post_list(request, slug=None):
@login_required
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'post_detail.html', {'post': post})
hit_count = HitCount.objects.get_for_object(post)
hit_count_response = HitCountMixin.hit_count(request, hit_count)
return render(request, 'post_detail.html', locals())
@login_required
@@ -72,6 +77,7 @@ def post_new(request):
post = form.save(commit=False)
post.author = request.user
post.save()
form.save_m2m()
return redirect('post_detail', pk=post.pk)
else:
form = PostForm()
@@ -121,26 +127,39 @@ def post_remove(request, pk):
@login_required
def tag_remove(request, slug=None):
log = logging.getLogger('mysite')
user_instance = get_object_or_404(CustomUser, user=request.user)
log.info(u)
tag = Tag.get_object_or_404(Tag, slug = slug)
log.info(tag)
if slug:
tag = get_object_or_404(Tag, slug=slug)
tag.delete()
user_instance.tags.remove(tag)
save_m2m()
return redirect('student_page')
return redirect('student_page')
@login_required
def student_page(request):
log = logging.getLogger('mysite')
user_instance = get_object_or_404(CustomUser, user=request.user)
if request.method == "POST":
log.info('post method')
form = NewTagForm(request.POST, instance=user_instance)
if form.is_valid():
log.info('form is valid')
obj = form.save(commit=False)
obj.user = request.user
obj.save()
m_tags = form.cleaned_data['m_tags']
tags = Tag.objects.all()
if m_tags in tags:
obj.tags.add(*m_tags)
tag_names = [tag.name for tag in Tag.objects.all()]
log.info(tag_names)
m_tags = form.cleaned_data['tags']
m_tags = ' '.join(str(m_tags) for m_tags in m_tags)
log.info(m_tags)
if m_tags in tag_names:
log.info('worked out')
obj.tags.add(m_tags)
form.save_m2m()
messages.info(request, 'Der Tag "' + m_tags + '" wurde gespeichert')
return redirect('/student/')
else:
messages.info(request, 'Sorry !! Den Tag den du suchst gibt es leider nicht!')
@@ -159,27 +178,6 @@ def tag_list(request):
posts = Post.objects.filter(tags__in=[tag]).order_by('-published_date')
log.info(posts)
return render(request, 'tag_list.html', locals())
class TagSearch(TagCanvasListView):
template_name = 'blog_search_list_view.html'
model = Post
context_object_name = 'list'
paginate_by = 8
def get_context_data(self, **kwargs):
context = super(CustomerSearch, self).get_context_data(**kwargs)
context = self.get_queryset()
return context
def get_queryset(self):
cObj = Customer.objects.all()
var_get_search = self.request.GET.get('search_box')
if var_get_search is not None:
cObj = cObj.filter(firstname__icontains=var_get_search)
if var_get_order_by is not None:
cObj = cObj.order_by(var_get_order_by)
return cObj
def blog_search_list_view(request):
log = logging.getLogger('mysite')
@@ -192,3 +190,11 @@ def blog_search_list_view(request):
def tag_cloud(request):
return render(request, 'tag_cloud.html', {})
mail.send(
'kleinhenz.e@gmail.com', # List of email addresses also accepted
'esther.kleinhenz@web.de',
subject='My email',
message='Hi there!',
html_message='Hi <strong>there</strong>!',
)
File renamed without changes.
@@ -0,0 +1,36 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\contentsline {chapter}{Acknowledgements}{iii}{chapter*.2}}
\@setckpt{AcknowledgmentsDedicationSentence/acknowledgements}{
\setcounter{page}{4}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
\setcounter{enumiii}{0}
\setcounter{enumiv}{0}
\setcounter{footnote}{0}
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{0}
\setcounter{section}{0}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{table}{0}
\setcounter{float@type}{8}
\setcounter{parentequation}{0}
\setcounter{lstnumber}{1}
\setcounter{ContinuedFloat}{0}
\setcounter{subfigure}{0}
\setcounter{subtable}{0}
\setcounter{r@tfl@t}{0}
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{Hy@AnnotLevel}{0}
\setcounter{bookmark@seq@number}{2}
\setcounter{NAT@ctr}{0}
\setcounter{lstlisting}{0}
\setcounter{section@level}{0}
}
@@ -24,7 +24,7 @@
\babel@aux{german}{}
\@input{abstract/abstract.aux}
\HyPL@Entry{2<</S/r>>}
\HyPL@Entry{6<</S/D>>}
\HyPL@Entry{5<</S/D>>}
\@writefile{toc}{\contentsline {chapter}{Abbildungsverzeichnis}{1}{chapter*.4}}
\@input{chapters/einleitung.aux}
\@input{chapters/framework.aux}
@@ -35,13 +35,25 @@
\bibstyle{alpha}
\citation{*}
\bibdata{references/References_2}
\bibcite{Bootstrap}{{BA11}{}{{}}{{}}}
\bibcite{Computer}{{Com18}{}{{}}{{}}}
\bibcite{Oracle}{{Coo10}{}{{}}{{}}}
\bibcite{Dixit}{{Dix18}{}{{}}{{}}}
\bibcite{Python}{{Fou18}{}{{}}{{}}}
\bibcite{Schlede}{{FMS17}{}{{}}{{}}}
\bibcite{Django}{{Fou18a}{}{{}}{{}}}
\bibcite{Python}{{Fou18b}{}{{}}{{}}}
\bibcite{Gaynor}{{Gay10}{}{{}}{{}}}
\bibcite{Herzog}{{Her16}{}{{}}{{}}}
\bibcite{King}{{Kin17}{}{{}}{{}}}
\bibcite{Leipner}{{Lei13}{}{{}}{{}}}
\@writefile{toc}{\contentsline {chapter}{Referenzen}{21}{chapter*.14}}
\bibcite{Microsoft}{{Mic18}{}{{}}{{}}}
\bibcite{Ndukwe}{{Ndu17}{}{{}}{{}}}
\bibcite{Ong}{{Ong18}{}{{}}{{}}}
\bibcite{Otto}{{Ott11}{}{{}}{{}}}
\bibcite{Shabda}{{Sha09}{}{{}}{{}}}
\bibcite{Shelest}{{She09}{}{{}}{{}}}
\bibcite{Solutions}{{Sol17}{}{{}}{{}}}
\bibcite{Timm}{{Tim15}{}{{}}{{}}}
\bibcite{Weigend}{{Wei17}{}{{}}{{}}}
\providecommand\NAT@force@numbers{}\NAT@force@numbers
\@writefile{toc}{\contentsline {chapter}{Referenzen}{19}{chapter*.11}}
@@ -1,4 +1,22 @@
\begin{thebibliography}{Ndu17}
\begin{thebibliography}{Fou18b}
\bibitem[BA11]{Bootstrap}
Twitter~Inc Bootstrap~Authors.
\newblock Bootstrap repository.
\newblock 2011.
\newblock https://github.com/twbs/bootstrap.
\bibitem[Com18]{Computer}
The Computer~Language Company.
\newblock Definition of: user permissions.
\newblock 2018.
\newblock https://www.pcmag.com/encyclopedia/term/58231/user-permissions.
\bibitem[Coo10]{Oracle}
Oracle Cooperation.
\newblock About ldap.
\newblock 2010.
\newblock https://docs.oracle.com/cd/E19182-01/820-6573/6nht2e5a4/index.html.
\bibitem[Dix18]{Dixit}
Chitrank Dixit.
@@ -7,7 +25,20 @@ Chitrank Dixit.
\newblock
https://www.datacamp.com/community/tutorials/pep8-tutorial-python-code.
\bibitem[Fou18]{Python}
\bibitem[FMS17]{Schlede}
Andreas~Donner Frank-Michael~Schlede, Thomas~Bär.
\newblock Was ist ldap (lightweight directory access protocol)?
\newblock 2017.
\newblock
https://www.ip-insider.de/was-ist-ldap-lightweight-directory-access-protocol-a-581204/.
\bibitem[Fou18a]{Django}
Django~Software Foundation.
\newblock django.contrib.auth, user model.
\newblock 2018.
\newblock https://docs.djangoproject.com/en/2.1/ref/contrib/auth/.
\bibitem[Fou18b]{Python}
Python~Software Foundation.
\newblock Virtual environments and packages.
\newblock 2018.
@@ -42,6 +73,12 @@ Ingo Leipner.
\newblock
http://www.mz-web.de/wirtschaft/e-mail-flut-mails-bearbeiten-kommunikation-stress-zeit-sparen.
\bibitem[Mic18]{Microsoft}
Microsoft.
\newblock Extensions for the visual studio family of products.
\newblock 2018.
\newblock https://marketplace.visualstudio.com/.
\bibitem[Ndu17]{Ndukwe}
Nnenna Ndukwe.
\newblock Python is the back-end programming language of the future and
@@ -50,6 +87,25 @@ Nnenna Ndukwe.
\newblock
https://medium.com/@nnennahacks/https-medium-com-nnennandukwe-python-is-the-back-end-programming-language-of-the-future-heres-why.
\bibitem[Ong18]{Ong}
Selwin Ong.
\newblock django-post\_office git repository.
\newblock 2018.
\newblock https://github.com/ui/django-post\_office/blob/master/AUTHORS.rst.
\bibitem[Ott11]{Otto}
Mark Otto.
\newblock Bootstrap from twitter.
\newblock 2011.
\newblock
https://blog.twitter.com/developer/en\_us/a/2011/bootstrap-twitter.html.
\bibitem[Sha09]{Shabda}
Shabda.
\newblock Understanding decorators.
\newblock 2009.
\newblock https://www.agiliq.com/blog/2009/06/understanding-decorators/.
\bibitem[She09]{Shelest}
Alexy Shelest.
\newblock Model view controller, model view presenter, and model view viewmodel
@@ -58,4 +114,23 @@ Alexy Shelest.
\newblock
https://www.codeproject.com/Articles/42830/Model-View-Controller-Model-View-Presenter-and-Mod.
\bibitem[Sol17]{Solutions}
Mindfire Solutions.
\newblock Advantages and disadvantages of python programming language.
\newblock 2017.
\newblock
https://medium.com/@mindfiresolutions.usa/advantages-and-disadvantages-of-python-programming-language-fd0b394f2121.
\bibitem[Tim15]{Timm}
Damon Timm.
\newblock django-hitcount documentation.
\newblock 2015.
\newblock https://django-hitcount.readthedocs.io/en/latest/overview.html.
\bibitem[Wei17]{Weigend}
Michael Weigend.
\newblock {\em Python GE-PACKT}.
\newblock 2017.
\newblock Kapitel 23.1.
\end{thebibliography}
@@ -11,53 +11,77 @@ A level-1 auxiliary file: chapters/ausblick.aux
A level-1 auxiliary file: chapters/fazit.aux
The style file: alpha.bst
Database file #1: ../references/References_2.bib
Repeated entry---line 78 of file ../references/References_2.bib
: @article{Ong
: ,
I'm skipping whatever remains of this entry
Repeated entry---line 127 of file ../references/References_2.bib
: @article{Otto
: ,
I'm skipping whatever remains of this entry
Repeated entry---line 141 of file ../references/References_2.bib
: @article{Django
: ,
I'm skipping whatever remains of this entry
Warning--empty journal in Bootstrap
Warning--empty journal in Computer
Warning--empty journal in Oracle
Warning--empty journal in Dixit
Warning--empty journal in Schlede
Warning--empty journal in Django
Warning--empty journal in Python
Warning--empty journal in Gaynor
Warning--empty journal in Herzog
Warning--empty journal in King
Warning--empty journal in Leipner
Warning--empty journal in Microsoft
Warning--empty journal in Ndukwe
Warning--empty journal in Ong
Warning--empty journal in Otto
Warning--empty journal in Shabda
Warning--empty journal in Shelest
You've used 8 entries,
Warning--empty journal in Solutions
Warning--empty journal in Timm
Warning--empty publisher in Weigend
You've used 20 entries,
2543 wiz_defined-function locations,
611 strings with 6085 characters,
and the built_in function-call counts, 2196 in all, are:
= -- 216
> -- 64
< -- 8
+ -- 16
- -- 16
* -- 106
:= -- 390
add.period$ -- 32
call.type$ -- 8
change.case$ -- 40
chr.to.int$ -- 8
cite$ -- 16
duplicate$ -- 112
empty$ -- 161
format.name$ -- 32
if$ -- 426
int.to.chr$ -- 1
671 strings with 7567 characters,
and the built_in function-call counts, 5457 in all, are:
= -- 537
> -- 160
< -- 20
+ -- 41
- -- 40
* -- 264
:= -- 959
add.period$ -- 80
call.type$ -- 20
change.case$ -- 99
chr.to.int$ -- 19
cite$ -- 40
duplicate$ -- 282
empty$ -- 406
format.name$ -- 78
if$ -- 1065
int.to.chr$ -- 2
int.to.str$ -- 0
missing$ -- 8
newline$ -- 51
num.names$ -- 24
pop$ -- 48
missing$ -- 21
newline$ -- 123
num.names$ -- 60
pop$ -- 121
preamble$ -- 1
purify$ -- 48
purify$ -- 120
quote$ -- 0
skip$ -- 88
skip$ -- 217
stack$ -- 0
substring$ -- 56
swap$ -- 0
text.length$ -- 8
text.prefix$ -- 8
substring$ -- 140
swap$ -- 1
text.length$ -- 20
text.prefix$ -- 18
top$ -- 0
type$ -- 64
warning$ -- 8
while$ -- 16
width$ -- 10
write$ -- 106
(There were 8 warnings)
type$ -- 156
warning$ -- 20
while$ -- 40
width$ -- 25
write$ -- 262
(There were 3 error messages)
@@ -2,12 +2,15 @@
\babel@toc {german}{}
\addvspace {10\p@ }
\addvspace {10\p@ }
\contentsline {figure}{\numberline {2.1}{\ignorespaces Vereinfachter MVP\relax }}{6}{figure.caption.5}
\contentsline {figure}{\numberline {2.2}{\ignorespaces Request-Response-Kreislauf des Django Frameworks\relax }}{7}{figure.caption.6}
\contentsline {figure}{\numberline {2.3}{\ignorespaces Erstellen der virtuelle Umgebung im Terminal\relax }}{8}{figure.caption.7}
\contentsline {figure}{\numberline {2.4}{\ignorespaces Beispiel eines LDAP-Trees\relax }}{9}{figure.caption.8}
\contentsline {figure}{\numberline {2.1}{\ignorespaces Vereinfachter MVP ([She09])\relax }}{5}{figure.caption.5}
\contentsline {figure}{\numberline {2.2}{\ignorespaces Request-Response-Kreislauf des Django Frameworks ([Nev15])\relax }}{6}{figure.caption.6}
\contentsline {figure}{\numberline {2.3}{\ignorespaces Erstellen der virtuelle Umgebung im Terminal\relax }}{7}{figure.caption.7}
\contentsline {figure}{\numberline {2.4}{\ignorespaces Beispiel eines LDAP-Trees ([Orc10])\relax }}{8}{figure.caption.8}
\contentsline {figure}{\numberline {2.5}{\ignorespaces Einbindung von Bootstrap in einer HTML-Datei\relax }}{11}{figure.caption.9}
\contentsline {figure}{\numberline {2.6}{\ignorespaces Bootstrap-Klassen in HTML-Tag\relax }}{12}{figure.caption.10}
\contentsline {figure}{\numberline {2.6}{\ignorespaces Bootstrap-Klassen in HTML-Tag\relax }}{11}{figure.caption.10}
\addvspace {10\p@ }
\contentsline {figure}{\numberline {3.1}{\ignorespaces Forschungsdesign\relax }}{13}{figure.caption.11}
\contentsline {figure}{\numberline {3.2}{\ignorespaces CustomUserModel in models.py\relax }}{15}{figure.caption.12}
\contentsline {figure}{\numberline {3.3}{\ignorespaces Datenmodellierung von User und Post\relax }}{16}{figure.caption.13}
\addvspace {10\p@ }
\addvspace {10\p@ }
@@ -1,4 +1,4 @@
This is XeTeX, Version 3.14159265-2.6-0.99999 (TeX Live 2018) (preloaded format=xelatex 2018.6.7) 15 OCT 2018 21:49
This is XeTeX, Version 3.14159265-2.6-0.99999 (TeX Live 2018) (preloaded format=xelatex 2018.6.7) 9 NOV 2018 15:56
entering extended mode
\write18 enabled.
file:line:error style messages enabled.
@@ -902,7 +902,14 @@ Package: fancyhdr 2017/06/30 v3.9a Extensive control of page headers and footers
\f@nch@O@erf=\skip74
\f@nch@O@olf=\skip75
\f@nch@O@orf=\skip76
))
)
Package Fancyhdr Warning: \fancyhead's `E' option without twoside option is useless on input line 71.
Package Fancyhdr Warning: \fancyfoot's `E' option without twoside option is useless on input line 73.
)
./bachelorabeit_EstherKleinhenz.tex:5: Extra \else.
\@include ...ediate \closeout \@partaux \fi \else
\deadcycles \z@ \@nameuse ...
@@ -945,7 +952,7 @@ LaTeX Font Info: ... okay on input line 26.
* paper: a4paper
* layout: <same size as paper>
* layoutoffset:(h,v)=(0.0pt,0.0pt)
* modes: twoside
* modes:
* h-part:(L,W,R)=(56.9055pt, 426.79135pt, 113.81102pt)
* v-part:(T,H,B)=(85.35826pt, 674.33032pt, 85.35826pt)
* \paperwidth=597.50787pt
@@ -953,13 +960,13 @@ LaTeX Font Info: ... okay on input line 26.
* \textwidth=426.79135pt
* \textheight=674.33032pt
* \oddsidemargin=-15.36449pt
* \evensidemargin=41.54103pt
* \evensidemargin=-15.36449pt
* \topmargin=-23.91173pt
* \headheight=12.0pt
* \headsep=25.0pt
* \topskip=12.0pt
* \footskip=30.0pt
* \marginparwidth=85.0pt
* \marginparwidth=35.0pt
* \marginparsep=10.0pt
* \columnsep=10.0pt
* \skip\footins=10.8pt plus 4.0pt minus 2.0pt
@@ -967,8 +974,8 @@ LaTeX Font Info: ... okay on input line 26.
* \voffset=0.0pt
* \mag=1000
* \@twocolumnfalse
* \@twosidetrue
* \@mparswitchtrue
* \@twosidefalse
* \@mparswitchfalse
* \@reversemarginfalse
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
@@ -1043,17 +1050,17 @@ Underfull \hbox (badness 10000) in paragraph at lines 48--57
[]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 85.
(babel) in language on input line 82.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 85.
(babel) in language on input line 82.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 85.
(babel) in language on input line 82.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 85.
(babel) in language on input line 82.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 85.
(babel) in language on input line 82.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 85.
(babel) in language on input line 82.
[2])
\openout2 = `abstract/abstract.aux'.
@@ -1085,56 +1092,47 @@ Package babel Info: Redefining german shorthand "~
]
] (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.toc
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 49.
(babel) in language on input line 29.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 49.
(babel) in language on input line 29.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 49.
(babel) in language on input line 29.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 49.
(babel) in language on input line 29.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 49.
(babel) in language on input line 29.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 49.
(babel) in language on input line 29.
[2
] (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.toc)
])
\tf@toc=\write6
\openout6 = `bachelorabeit_EstherKleinhenz.toc'.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 50.
[3]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 50.
[4
]
Package Fancyhdr Warning: \headheight is too small (12.0pt):
Make it at least 14.49998pt.
We now make it that large for the rest of the document.
This may cause the page layout to be inconsistent, however.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 50.
[3]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 52.
Package babel Info: Redefining german shorthand "|
@@ -1160,27 +1158,12 @@ Package babel Info: Redefining german shorthand "~
[1
]
\openout2 = `chapters/einleitung.aux'.
(./chapters/einleitung.tex
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
[2
]
Kapitel 1.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
@@ -1210,35 +1193,32 @@ Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 11.
(babel) in language on input line 12.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 11.
(babel) in language on input line 12.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 11.
(babel) in language on input line 12.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 11.
(babel) in language on input line 12.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 11.
(babel) in language on input line 12.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 11.
[3]
(babel) in language on input line 12.
[2
]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 11.
(babel) in language on input line 13.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 11.
(babel) in language on input line 13.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 11.
(babel) in language on input line 13.
Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
)
Package Fancyhdr Warning: \headheight is too small (12.0pt):
Make it at least 14.49998pt.
We now make it that large for the rest of the document.
This may cause the page layout to be inconsistent, however.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 57.
Package babel Info: Redefining german shorthand "|
@@ -1251,7 +1231,7 @@ Package babel Info: Redefining german shorthand "|
(babel) in language on input line 57.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 57.
[4]
[3]
\openout2 = `chapters/framework.aux'.
(./chapters/framework.tex
@@ -1263,153 +1243,185 @@ Package babel Info: Redefining german shorthand "|
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 7.
(babel) in language on input line 6.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 7.
(babel) in language on input line 6.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 7.
(babel) in language on input line 6.
File: figures/MVP.png Graphic file (type bmp)
<figures/MVP.png>
LaTeX Warning: `!h' float specifier changed to `!ht'.
LaTeX Font Info: Try loading font information for TS1+aer on input line 21.
LaTeX Font Info: No file TS1aer.fd. on input line 21.
LaTeX Font Warning: Font shape `TS1/aer/m/n' undefined
(Font) using `TS1/cmr/m/n' instead
(Font) for symbol `textbullet' on input line 21.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 22.
(babel) in language on input line 20.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 22.
(babel) in language on input line 20.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 22.
(babel) in language on input line 20.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 22.
(babel) in language on input line 20.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 22.
(babel) in language on input line 20.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 22.
[5
(babel) in language on input line 20.
[4
]
LaTeX Font Info: Try loading font information for TS1+aer on input line 20.
LaTeX Font Info: No file TS1aer.fd. on input line 20.
LaTeX Font Warning: Font shape `TS1/aer/m/n' undefined
(Font) using `TS1/cmr/m/n' instead
(Font) for symbol `textbullet' on input line 20.
File: figures/request-response-cycle.png Graphic file (type bmp)
<figures/request-response-cycle.png>
LaTeX Warning: `!h' float specifier changed to `!ht'.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 36.
(babel) in language on input line 35.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 36.
(babel) in language on input line 35.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 36.
(babel) in language on input line 35.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 51.
(babel) in language on input line 38.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 51.
(babel) in language on input line 38.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 51.
(babel) in language on input line 38.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 51.
(babel) in language on input line 38.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 51.
(babel) in language on input line 38.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 51.
[6]
Underfull \vbox (badness 10000) has occurred while \output is active []
(babel) in language on input line 38.
[5]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 53.
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 53.
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 53.
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 53.
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 53.
(babel) in language on input line 50.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 53.
[7]
(babel) in language on input line 50.
[6]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 58.
(babel) in language on input line 54.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 58.
(babel) in language on input line 54.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 58.
(babel) in language on input line 54.
File: figures/virt-env-terminal.png Graphic file (type bmp)
<figures/virt-env-terminal.png>
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 80.
(babel) in language on input line 67.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 80.
(babel) in language on input line 67.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 80.
(babel) in language on input line 67.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 80.
(babel) in language on input line 70.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 80.
(babel) in language on input line 70.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 80.
[8]
(babel) in language on input line 70.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 70.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 70.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 70.
[7]
File: figures/ldap-tree.png Graphic file (type bmp)
<figures/ldap-tree.png>
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 78.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 78.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 78.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 81.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 81.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 81.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 83.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 83.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 83.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 83.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 83.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 83.
[8]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 95.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 95.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 95.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 97.
(babel) in language on input line 100.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 97.
(babel) in language on input line 100.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 97.
(babel) in language on input line 100.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 97.
(babel) in language on input line 100.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 97.
(babel) in language on input line 100.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 97.
(babel) in language on input line 100.
[9]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 118.
(babel) in language on input line 104.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 118.
(babel) in language on input line 104.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 118.
Underfull \vbox (badness 10000) has occurred while \output is active []
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 120.
[10]
(babel) in language on input line 104.
File: figures/bootstrap-head-tag.png Graphic file (type bmp)
<figures/bootstrap-head-tag.png>
File: figures/bootstrap-class-example.png Graphic file (type bmp)
<figures/bootstrap-class-example.png>
LaTeX Warning: `!h' float specifier changed to `!ht'.
File: figures/bootstrap-class-example.png Graphic file (type bmp)
<figures/bootstrap-class-example.png>
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 123.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 123.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 123.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 123.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 123.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 123.
[10]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 129.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 129.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 129.
)
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 58.
@@ -1424,19 +1436,6 @@ Package babel Info: Redefining german shorthand "|
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 58.
[11]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 58.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 58.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 58.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 58.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 58.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 58.
[12]
\openout2 = `chapters/prototyp.aux'.
(./chapters/prototyp.tex
@@ -1453,52 +1452,174 @@ Package babel Info: Redefining german shorthand "|
(babel) in language on input line 5.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 5.
File: figures/forschungsdesign.png Graphic file (type bmp)
<figures/forschungsdesign.png>
LaTeX Warning: `!h' float specifier changed to `!ht'.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 16.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 16.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 16.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 19.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 19.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 19.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 19.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 19.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 19.
[12
]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 27.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 27.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 27.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 27.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 27.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 27.
[13]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 31.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 31.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 31.
Underfull \hbox (badness 10000) in paragraph at lines 34--45
Overfull \hbox (6.80373pt too wide) in paragraph at lines 6--7
\T1/aer/m/n/12 Grundlegender Auf-bau der Web-site, Ver-wal-tung der Da-ten evlt auf noch-mal Taggable-
[]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 8.
(babel) in language on input line 52.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 8.
(babel) in language on input line 52.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 8.
(babel) in language on input line 52.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 11.
(babel) in language on input line 52.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 11.
(babel) in language on input line 52.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 11.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 15.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 15.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 15.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 18.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 18.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 18.
(babel) in language on input line 52.
[14]
File: figures/custommodelcode.png Graphic file (type bmp)
<figures/custommodelcode.png>
Overfull \hbox (1.12642pt too wide) in paragraph at lines 19--20
Underfull \hbox (badness 10000) in paragraph at lines 72--74
[]
File: figures/datamodel.png Graphic file (type bmp)
<figures/datamodel.png>
LaTeX Warning: `!h' float specifier changed to `!ht'.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 90.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 90.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 90.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 92.
[15]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 92.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 92.
[16]
Overfull \hbox (9.91238pt too wide) in paragraph at lines 93--94
[]\T1/aer/m/n/12 Ein Django-Projekt bil-det be-reits beim Ein-rich-ten, \T1/aer/m/it/12 per De-fault\T1/aer/m/n/12 , ei-ne Administrator-
[]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 96.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 96.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 96.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 106.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 106.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 106.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 109.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 109.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 109.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 114.
[17]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 114.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 114.
Overfull \hbox (1.12642pt too wide) in paragraph at lines 115--116
\T1/aer/m/n/12 Tags als ein-ge-logg-ter User abon-nie-ren und ver-wal-ten Front-end und Admin-Backend?
[]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 21.
(babel) in language on input line 117.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 21.
(babel) in language on input line 117.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 21.
(babel) in language on input line 117.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 24.
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 24.
(babel) in language on input line 120.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 24.
(babel) in language on input line 120.
)
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 59.
@@ -1512,25 +1633,7 @@ Package babel Info: Redefining german shorthand "|
(babel) in language on input line 59.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 59.
[13
]
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 59.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 59.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 59.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 59.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 59.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 59.
[14]
[18]
\openout2 = `chapters/ergebnis.aux'.
(./chapters/ergebnis.tex
@@ -1564,7 +1667,7 @@ Package babel Info: Redefining german shorthand "|
(babel) in language on input line 60.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 60.
[15
[19
@@ -1576,26 +1679,6 @@ Package babel Info: Redefining german shorthand "~
\openout2 = `chapters/fazit.aux'.
(./chapters/fazit.tex
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
[16
]
Kapitel 5.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
@@ -1616,24 +1699,14 @@ Package babel Info: Redefining german shorthand "|
(babel) in language on input line 62.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 62.
[17] (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.bbl
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
[18
[20
]
] (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.bbl
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 1.
Package babel Info: Redefining german shorthand "|
@@ -1641,37 +1714,84 @@ Package babel Info: Redefining german shorthand "|
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 1.
Underfull \hbox (badness 10000) in paragraph at lines 4--9
Underfull \hbox (badness 4316) in paragraph at lines 4--8
[]\T1/aer/m/n/12 Twitter Inc Boot-strap Aut-hors. Boot-strap re-po-si-to-ry. 2011.
[]
Underfull \hbox (badness 10000) in paragraph at lines 16--20
[]\T1/aer/m/n/12 Oracle Coope-ra-ti-on. About ldap. 2010.
[]
Underfull \hbox (badness 10000) in paragraph at lines 22--27
\T1/aer/m/n/12 https://www.datacamp.com/community/tutorials/pep8-tutorial-python-
[]
Missing character: There is no ’ in font aer12!
Underfull \hbox (badness 1521) in paragraph at lines 31--36
[]\T1/aer/m/n/12 Adam King. Djan-go midd-le-wa-res and the re-quest/-re-spon-se cy-cle.
Underfull \hbox (badness 2717) in paragraph at lines 62--67
[]\T1/aer/m/n/12 Adam King. Djan-go midd-le-wa-res and the re-quest/-re-spon-se cy-
[]
Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
Missing character: There is no ̈ in font aer12!
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 75.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 75.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 75.
Package babel Info: Redefining german shorthand "f
(babel) in language on input line 75.
Package babel Info: Redefining german shorthand "|
(babel) in language on input line 75.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 75.
[21
]
Missing character: There is no ’ in font aer12!
Underfull \hbox (badness 10000) in paragraph at lines 46--52
Underfull \hbox (badness 10000) in paragraph at lines 83--89
\T1/aer/m/n/12 medium-com-nnennandukwe-python-is-the-back-end-programming-
[]
Underfull \hbox (badness 10000) in paragraph at lines 54--60
Underfull \hbox (badness 10000) in paragraph at lines 91--95
[]\T1/aer/m/n/12 Selwin Ong. django-post_office git re-po-si-to-ry. 2018.
[]
Underfull \hbox (badness 10000) in paragraph at lines 97--102
[]\T1/aer/m/n/12 Mark Ot-to. Boot-strap from twit-ter. 2011.
[]
Underfull \hbox (badness 10000) in paragraph at lines 97--102
\T1/aer/m/n/12 https://blog.twitter.com/developer/en_us/a/2011/bootstrap-
[]
Underfull \hbox (badness 10000) in paragraph at lines 104--108
[]\T1/aer/m/n/12 Shabda. Un-der-stan-ding de-co-ra-tors. 2009.
[]
Underfull \hbox (badness 10000) in paragraph at lines 110--116
[]\T1/aer/m/n/12 Alexy She-lest. Mo-del view con-trol-ler, mo-del view pre-
[]
Underfull \hbox (badness 10000) in paragraph at lines 54--60
Underfull \hbox (badness 10000) in paragraph at lines 110--116
\T1/aer/m/n/12 sen-ter, and mo-del view view-mo-del de-sign pat-terns. 2009.
[]
Underfull \hbox (badness 10000) in paragraph at lines 54--60
Underfull \hbox (badness 10000) in paragraph at lines 110--116
\T1/aer/m/n/12 https://www.codeproject.com/Articles/42830/Model-View-Controller-
[]
@@ -1689,7 +1809,7 @@ Package babel Info: Redefining german shorthand "|
(babel) in language on input line 77.
Package babel Info: Redefining german shorthand "~
(babel) in language on input line 77.
[19]
[22]
Package atveryend Info: Empty hook `AfterLastShipout' on input line 77.
(/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.aux (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/titlepage/titlepage.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/abstract/abstract.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/chapters/einleitung.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/chapters/framework.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/chapters/prototyp.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/chapters/ergebnis.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/chapters/ausblick.aux) (/Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/chapters/fazit.aux))
Package atveryend Info: Empty hook `AtVeryEndDocument' on input line 77.
@@ -1704,12 +1824,12 @@ Package atveryend Info: Empty hook `AtVeryVeryEnd' on input line 77.
)
(\end occurred when \ifnum on line 5 was incomplete)
Here is how much of TeX's memory you used:
26561 strings out of 492970
476474 string characters out of 6133939
547394 words of memory out of 5000000
30175 multiletter control sequences out of 15000+600000
554220 words of font info for 61 fonts, out of 8000000 for 9000
26612 strings out of 492970
477352 string characters out of 6133939
547177 words of memory out of 5000000
30222 multiletter control sequences out of 15000+600000
555998 words of font info for 62 fonts, out of 8000000 for 9000
1348 hyphenation exceptions out of 8191
58i,11n,50p,10437b,892s stack positions out of 5000i,500n,10000p,200000b,80000s
58i,12n,50p,10437b,958s stack positions out of 5000i,500n,10000p,200000b,80000s
Output written on /Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.pdf (25 pages).
Output written on /Users/Esthi/thesis_ek/doc/bachelorarbeit_EstherKleinhenz/.texpadtmp/bachelorabeit_EstherKleinhenz.pdf (27 pages).
@@ -5,19 +5,26 @@
\BOOKMARK [1][-]{section.1.2}{Ziel\040der\040Arbeit}{chapter.1}% 5
\BOOKMARK [0][-]{chapter.2}{Framework}{}% 6
\BOOKMARK [1][-]{section.2.1}{Django}{chapter.2}% 7
\BOOKMARK [2][-]{subsection.2.1.1}{Besonderheiten}{section.2.1}% 8
\BOOKMARK [1][-]{section.2.2}{Erweiterungen}{chapter.2}% 9
\BOOKMARK [2][-]{subsection.2.2.1}{Taggable-Manager}{section.2.2}% 10
\BOOKMARK [1][-]{section.2.3}{Bootstrap}{chapter.2}% 11
\BOOKMARK [0][-]{chapter.3}{Prototyp}{}% 12
\BOOKMARK [1][-]{section.3.1}{Organisation}{chapter.3}% 13
\BOOKMARK [2][-]{subsection.3.1.1}{Verwaltung\040im\040Administrator-Backend}{section.3.1}% 14
\BOOKMARK [2][-]{subsection.3.1.2}{Berechtigung\040der\040User}{section.3.1}% 15
\BOOKMARK [1][-]{section.3.2}{Funktion}{chapter.3}% 16
\BOOKMARK [2][-]{subsection.3.2.1}{Abonnieren}{section.3.2}% 17
\BOOKMARK [2][-]{subsection.3.2.2}{Filtern}{section.3.2}% 18
\BOOKMARK [2][-]{subsection.3.2.3}{Benachrichtigung}{section.3.2}% 19
\BOOKMARK [0][-]{chapter.4}{Ergebnis}{}% 20
\BOOKMARK [1][-]{subsection.4.0.1}{Evaluierung}{chapter.4}% 21
\BOOKMARK [0][-]{chapter.5}{Zusammenfassung\040und\040Ausblick}{}% 22
\BOOKMARK [0][-]{chapter*.11}{Referenzen}{}% 23
\BOOKMARK [2][-]{subsection.2.1.1}{Besonderheiten\040Django's}{section.2.1}% 8
\BOOKMARK [2][-]{subsection.2.1.2}{Virtuelle\040Umgebung}{section.2.1}% 9
\BOOKMARK [2][-]{subsection.2.1.3}{Lightweight\040Directory\040Access\040Protocol}{section.2.1}% 10
\BOOKMARK [1][-]{section.2.2}{Erweiterungen}{chapter.2}% 11
\BOOKMARK [2][-]{subsection.2.2.1}{Taggable-Manager}{section.2.2}% 12
\BOOKMARK [2][-]{subsection.2.2.2}{Hilfsbibliotheken}{section.2.2}% 13
\BOOKMARK [1][-]{section.2.3}{Bootstrap}{chapter.2}% 14
\BOOKMARK [1][-]{section.2.4}{Cron}{chapter.2}% 15
\BOOKMARK [0][-]{chapter.3}{Prototyp}{}% 16
\BOOKMARK [1][-]{section.3.1}{Forschungsdesign}{chapter.3}% 17
\BOOKMARK [1][-]{section.3.2}{Organisation}{chapter.3}% 18
\BOOKMARK [2][-]{subsection.3.2.1}{Datenmodellierung}{section.3.2}% 19
\BOOKMARK [2][-]{subsection.3.2.2}{Verwaltung\040im\040Administrator-Back-end}{section.3.2}% 20
\BOOKMARK [2][-]{subsection.3.2.3}{Berechtigung\040der\040User}{section.3.2}% 21
\BOOKMARK [1][-]{section.3.3}{Funktionen}{chapter.3}% 22
\BOOKMARK [2][-]{subsection.3.3.1}{Verwalten}{section.3.3}% 23
\BOOKMARK [2][-]{subsection.3.3.2}{Abonnieren}{section.3.3}% 24
\BOOKMARK [2][-]{subsection.3.3.3}{Filtern}{section.3.3}% 25
\BOOKMARK [2][-]{subsection.3.3.4}{Benachrichtigung}{section.3.3}% 26
\BOOKMARK [0][-]{chapter.4}{Ergebnis}{}% 27
\BOOKMARK [1][-]{subsection.4.0.1}{Evaluierung}{chapter.4}% 28
\BOOKMARK [0][-]{chapter.5}{Zusammenfassung\040und\040Ausblick}{}% 29
\BOOKMARK [0][-]{chapter*.14}{Referenzen}{}% 30
@@ -2,24 +2,31 @@
\babel@toc {german}{}
\contentsline {chapter}{Abstract}{i}{figure.caption.1}
\contentsline {chapter}{Abbildungsverzeichnis}{1}{chapter*.4}
\contentsline {chapter}{\numberline {1}Einleitung}{3}{chapter.1}
\contentsline {section}{\numberline {1.1}Ausgangssituation}{3}{section.1.1}
\contentsline {section}{\numberline {1.2}Ziel der Arbeit}{4}{section.1.2}
\contentsline {chapter}{\numberline {2}Framework}{5}{chapter.2}
\contentsline {section}{\numberline {2.1}Django}{5}{section.2.1}
\contentsline {subsection}{\numberline {2.1.1}Besonderheiten}{6}{subsection.2.1.1}
\contentsline {chapter}{\numberline {1}Einleitung}{2}{chapter.1}
\contentsline {section}{\numberline {1.1}Ausgangssituation}{2}{section.1.1}
\contentsline {section}{\numberline {1.2}Ziel der Arbeit}{3}{section.1.2}
\contentsline {chapter}{\numberline {2}Framework}{4}{chapter.2}
\contentsline {section}{\numberline {2.1}Django}{4}{section.2.1}
\contentsline {subsection}{\numberline {2.1.1}Besonderheiten Django's}{6}{subsection.2.1.1}
\contentsline {subsection}{\numberline {2.1.2}Virtuelle Umgebung}{7}{subsection.2.1.2}
\contentsline {subsection}{\numberline {2.1.3}Lightweight Directory Access Protocol}{7}{subsection.2.1.3}
\contentsline {section}{\numberline {2.2}Erweiterungen}{8}{section.2.2}
\contentsline {subsection}{\numberline {2.2.1}Taggable-Manager}{10}{subsection.2.2.1}
\contentsline {section}{\numberline {2.3}Bootstrap}{11}{section.2.3}
\contentsline {chapter}{\numberline {3}Prototyp}{13}{chapter.3}
\contentsline {section}{\numberline {3.1}Organisation}{13}{section.3.1}
\contentsline {subsection}{\numberline {3.1.1}Verwaltung im Administrator-Backend}{13}{subsection.3.1.1}
\contentsline {subsection}{\numberline {3.1.2}Berechtigung der User}{13}{subsection.3.1.2}
\contentsline {section}{\numberline {3.2}Funktion}{13}{section.3.2}
\contentsline {subsection}{\numberline {3.2.1}Abonnieren}{13}{subsection.3.2.1}
\contentsline {subsection}{\numberline {3.2.2}Filtern}{13}{subsection.3.2.2}
\contentsline {subsection}{\numberline {3.2.3}Benachrichtigung}{14}{subsection.3.2.3}
\contentsline {chapter}{\numberline {4}Ergebnis}{15}{chapter.4}
\contentsline {subsection}{\numberline {4.0.1}Evaluierung}{15}{subsection.4.0.1}
\contentsline {chapter}{\numberline {5}Zusammenfassung und Ausblick}{17}{chapter.5}
\contentsline {chapter}{Referenzen}{19}{chapter*.11}
\contentsline {subsection}{\numberline {2.2.1}Taggable-Manager}{8}{subsection.2.2.1}
\contentsline {subsection}{\numberline {2.2.2}Hilfsbibliotheken}{9}{subsection.2.2.2}
\contentsline {section}{\numberline {2.3}Bootstrap}{10}{section.2.3}
\contentsline {section}{\numberline {2.4}Cron}{11}{section.2.4}
\contentsline {chapter}{\numberline {3}Prototyp}{12}{chapter.3}
\contentsline {section}{\numberline {3.1}Forschungsdesign}{12}{section.3.1}
\contentsline {section}{\numberline {3.2}Organisation}{13}{section.3.2}
\contentsline {subsection}{\numberline {3.2.1}Datenmodellierung}{14}{subsection.3.2.1}
\contentsline {subsection}{\numberline {3.2.2}Verwaltung im Administrator-Back-end}{16}{subsection.3.2.2}
\contentsline {subsection}{\numberline {3.2.3}Berechtigung der User}{17}{subsection.3.2.3}
\contentsline {section}{\numberline {3.3}Funktionen}{17}{section.3.3}
\contentsline {subsection}{\numberline {3.3.1}Verwalten}{18}{subsection.3.3.1}
\contentsline {subsection}{\numberline {3.3.2}Abonnieren}{18}{subsection.3.3.2}
\contentsline {subsection}{\numberline {3.3.3}Filtern}{18}{subsection.3.3.3}
\contentsline {subsection}{\numberline {3.3.4}Benachrichtigung}{18}{subsection.3.3.4}
\contentsline {chapter}{\numberline {4}Ergebnis}{19}{chapter.4}
\contentsline {subsection}{\numberline {4.0.1}Evaluierung}{19}{subsection.4.0.1}
\contentsline {chapter}{\numberline {5}Zusammenfassung und Ausblick}{20}{chapter.5}
\contentsline {chapter}{Referenzen}{21}{chapter*.14}
@@ -1,7 +1,7 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@setckpt{chapters/ausblick}{
\setcounter{page}{16}
\setcounter{page}{20}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -28,7 +28,7 @@
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{Hy@AnnotLevel}{0}
\setcounter{bookmark@seq@number}{21}
\setcounter{bookmark@seq@number}{28}
\setcounter{NAT@ctr}{0}
\setcounter{lstlisting}{0}
\setcounter{section@level}{0}
@@ -1,12 +1,12 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\contentsline {chapter}{\numberline {1}Einleitung}{3}{chapter.1}}
\@writefile{toc}{\contentsline {chapter}{\numberline {1}Einleitung}{2}{chapter.1}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\@writefile{toc}{\contentsline {section}{\numberline {1.1}Ausgangssituation}{3}{section.1.1}}
\@writefile{toc}{\contentsline {section}{\numberline {1.2}Ziel der Arbeit}{4}{section.1.2}}
\@writefile{toc}{\contentsline {section}{\numberline {1.1}Ausgangssituation}{2}{section.1.1}}
\@writefile{toc}{\contentsline {section}{\numberline {1.2}Ziel der Arbeit}{3}{section.1.2}}
\@setckpt{chapters/einleitung}{
\setcounter{page}{5}
\setcounter{page}{4}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -1,12 +1,12 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\contentsline {chapter}{\numberline {4}Ergebnis}{15}{chapter.4}}
\@writefile{toc}{\contentsline {chapter}{\numberline {4}Ergebnis}{19}{chapter.4}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\newlabel{ch:ergebnis}{{4}{15}{Ergebnis}{chapter.4}{}}
\@writefile{toc}{\contentsline {subsection}{\numberline {4.0.1}Evaluierung}{15}{subsection.4.0.1}}
\newlabel{ch:ergebnis}{{4}{19}{Ergebnis}{chapter.4}{}}
\@writefile{toc}{\contentsline {subsection}{\numberline {4.0.1}Evaluierung}{19}{subsection.4.0.1}}
\@setckpt{chapters/ergebnis}{
\setcounter{page}{16}
\setcounter{page}{20}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -33,7 +33,7 @@
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{Hy@AnnotLevel}{0}
\setcounter{bookmark@seq@number}{21}
\setcounter{bookmark@seq@number}{28}
\setcounter{NAT@ctr}{0}
\setcounter{lstlisting}{0}
\setcounter{section@level}{0}
@@ -1,11 +1,11 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\contentsline {chapter}{\numberline {5}Zusammenfassung und Ausblick}{17}{chapter.5}}
\@writefile{toc}{\contentsline {chapter}{\numberline {5}Zusammenfassung und Ausblick}{20}{chapter.5}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\newlabel{ch:zusammenfassung}{{5}{17}{Zusammenfassung und Ausblick}{chapter.5}{}}
\newlabel{ch:zusammenfassung}{{5}{20}{Zusammenfassung und Ausblick}{chapter.5}{}}
\@setckpt{chapters/fazit}{
\setcounter{page}{18}
\setcounter{page}{21}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -32,7 +32,7 @@
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{Hy@AnnotLevel}{0}
\setcounter{bookmark@seq@number}{22}
\setcounter{bookmark@seq@number}{29}
\setcounter{NAT@ctr}{0}
\setcounter{lstlisting}{0}
\setcounter{section@level}{0}
@@ -1,22 +1,26 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\contentsline {chapter}{\numberline {2}Framework}{5}{chapter.2}}
\@writefile{toc}{\contentsline {chapter}{\numberline {2}Framework}{4}{chapter.2}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\newlabel{ch:framework}{{2}{5}{Framework}{chapter.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {2.1}Django}{5}{section.2.1}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.1}{\ignorespaces Vereinfachter MVP\relax }}{6}{figure.caption.5}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1.1}Besonderheiten}{6}{subsection.2.1.1}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.2}{\ignorespaces Request-Response-Kreislauf des Django Frameworks\relax }}{7}{figure.caption.6}}
\newlabel{ch:framework}{{2}{4}{Framework}{chapter.2}{}}
\@writefile{toc}{\contentsline {section}{\numberline {2.1}Django}{4}{section.2.1}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.1}{\ignorespaces Vereinfachter MVP ([She09])\relax }}{5}{figure.caption.5}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.2}{\ignorespaces Request-Response-Kreislauf des Django Frameworks ([Nev15])\relax }}{6}{figure.caption.6}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1.1}Besonderheiten Django's}{6}{subsection.2.1.1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1.2}Virtuelle Umgebung}{7}{subsection.2.1.2}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.3}{\ignorespaces Erstellen der virtuelle Umgebung im Terminal\relax }}{7}{figure.caption.7}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.1.3}Lightweight Directory Access Protocol}{7}{subsection.2.1.3}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.4}{\ignorespaces Beispiel eines LDAP-Trees ([Orc10])\relax }}{8}{figure.caption.8}}
\@writefile{toc}{\contentsline {section}{\numberline {2.2}Erweiterungen}{8}{section.2.2}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.3}{\ignorespaces Erstellen der virtuelle Umgebung im Terminal\relax }}{8}{figure.caption.7}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.4}{\ignorespaces Beispiel eines LDAP-Trees\relax }}{9}{figure.caption.8}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.1}Taggable-Manager}{10}{subsection.2.2.1}}
\@writefile{toc}{\contentsline {section}{\numberline {2.3}Bootstrap}{11}{section.2.3}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.1}Taggable-Manager}{8}{subsection.2.2.1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {2.2.2}Hilfsbibliotheken}{9}{subsection.2.2.2}}
\@writefile{toc}{\contentsline {section}{\numberline {2.3}Bootstrap}{10}{section.2.3}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.5}{\ignorespaces Einbindung von Bootstrap in einer HTML-Datei\relax }}{11}{figure.caption.9}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.6}{\ignorespaces Bootstrap-Klassen in HTML-Tag\relax }}{12}{figure.caption.10}}
\@writefile{lof}{\contentsline {figure}{\numberline {2.6}{\ignorespaces Bootstrap-Klassen in HTML-Tag\relax }}{11}{figure.caption.10}}
\@writefile{toc}{\contentsline {section}{\numberline {2.4}Cron}{11}{section.2.4}}
\@setckpt{chapters/framework}{
\setcounter{page}{13}
\setcounter{page}{12}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -26,7 +30,7 @@
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{2}
\setcounter{section}{3}
\setcounter{section}{4}
\setcounter{subsection}{0}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
@@ -43,7 +47,7 @@
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{Hy@AnnotLevel}{0}
\setcounter{bookmark@seq@number}{11}
\setcounter{bookmark@seq@number}{15}
\setcounter{NAT@ctr}{0}
\setcounter{lstlisting}{0}
\setcounter{section@level}{0}
@@ -1,18 +1,24 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@writefile{toc}{\contentsline {chapter}{\numberline {3}Prototyp}{13}{chapter.3}}
\@writefile{toc}{\contentsline {chapter}{\numberline {3}Prototyp}{12}{chapter.3}}
\@writefile{lof}{\addvspace {10\p@ }}
\@writefile{lot}{\addvspace {10\p@ }}
\newlabel{ch:prototyp}{{3}{13}{Prototyp}{chapter.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {3.1}Organisation}{13}{section.3.1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.1.1}Verwaltung im Administrator-Backend}{13}{subsection.3.1.1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.1.2}Berechtigung der User}{13}{subsection.3.1.2}}
\@writefile{toc}{\contentsline {section}{\numberline {3.2}Funktion}{13}{section.3.2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2.1}Abonnieren}{13}{subsection.3.2.1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2.2}Filtern}{13}{subsection.3.2.2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2.3}Benachrichtigung}{14}{subsection.3.2.3}}
\newlabel{ch:prototyp}{{3}{12}{Prototyp}{chapter.3}{}}
\@writefile{toc}{\contentsline {section}{\numberline {3.1}Forschungsdesign}{12}{section.3.1}}
\@writefile{lof}{\contentsline {figure}{\numberline {3.1}{\ignorespaces Forschungsdesign\relax }}{13}{figure.caption.11}}
\@writefile{toc}{\contentsline {section}{\numberline {3.2}Organisation}{13}{section.3.2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2.1}Datenmodellierung}{14}{subsection.3.2.1}}
\@writefile{lof}{\contentsline {figure}{\numberline {3.2}{\ignorespaces CustomUserModel in models.py\relax }}{15}{figure.caption.12}}
\@writefile{lof}{\contentsline {figure}{\numberline {3.3}{\ignorespaces Datenmodellierung von User und Post\relax }}{16}{figure.caption.13}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2.2}Verwaltung im Administrator-Back-end}{16}{subsection.3.2.2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.2.3}Berechtigung der User}{17}{subsection.3.2.3}}
\@writefile{toc}{\contentsline {section}{\numberline {3.3}Funktionen}{17}{section.3.3}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3.1}Verwalten}{18}{subsection.3.3.1}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3.2}Abonnieren}{18}{subsection.3.3.2}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3.3}Filtern}{18}{subsection.3.3.3}}
\@writefile{toc}{\contentsline {subsection}{\numberline {3.3.4}Benachrichtigung}{18}{subsection.3.3.4}}
\@setckpt{chapters/prototyp}{
\setcounter{page}{15}
\setcounter{page}{19}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -22,12 +28,12 @@
\setcounter{mpfootnote}{0}
\setcounter{part}{0}
\setcounter{chapter}{3}
\setcounter{section}{2}
\setcounter{subsection}{3}
\setcounter{section}{3}
\setcounter{subsection}{4}
\setcounter{subsubsection}{0}
\setcounter{paragraph}{0}
\setcounter{subparagraph}{0}
\setcounter{figure}{0}
\setcounter{figure}{3}
\setcounter{table}{0}
\setcounter{float@type}{8}
\setcounter{parentequation}{0}
@@ -39,7 +45,7 @@
\setcounter{Item}{0}
\setcounter{Hfootnote}{0}
\setcounter{Hy@AnnotLevel}{0}
\setcounter{bookmark@seq@number}{19}
\setcounter{bookmark@seq@number}{26}
\setcounter{NAT@ctr}{0}
\setcounter{lstlisting}{0}
\setcounter{section@level}{0}
@@ -1,7 +1,7 @@
\relax
\providecommand\hyper@newdestlabel[2]{}
\@setckpt{titlepage/titlepage}{
\setcounter{page}{3}
\setcounter{page}{1}
\setcounter{equation}{0}
\setcounter{enumi}{0}
\setcounter{enumii}{0}
@@ -1,5 +1,5 @@
\RequirePackage[ngerman=ngerman-x-latest]{hyphsubst}
\documentclass[12pt,a4paper,openright,twoside]{report}
\documentclass[12pt,a4paper,openright,oneside]{report}
%------------------------------------------------------------------------------
% preambule
\include{preambule/thesis.preamble}
@@ -1,13 +1,15 @@
\chapter{Einleitung}
\textcolor{red}{Die vorliegende Arbeit beschäftigt sich mit der wachsenden E-Mail-Flut in den Postfächern der Studierenden und wie man diese reduzieren kann.}
Schon seit geraumer Zeit ist bekannt, dass das Versenden von Informationen über elektronische Post nicht nur Vorteile mit sich bringt. Wie der Spezialist für Gesundheitsprozessberatung in einem Bericht der Mitteldeutschen Zeitung erwähnt, \glqq macht es die stets wachsende E-Mail-Menge unmöglich sich vernünftig mit den Informationen zu befassen"([Ver13]). Nicht nur am Arbeitsplatz sondern auch in Hochschulen wird Gebrauch gemacht, weitere Empfänger oder sogar ganze Verteiler mit in die Kopie einer E-Mail zu integrieren. Um die Prioritäten der Informationen besser bilden zu können, sollen Studierende selbst entscheiden, welche Nachrichten relevant sind. Hierfür wird eine Social Media Plattform mit personalisierbarem Dashboard implementiert.
Schon seit geraumer Zeit ist bekannt, dass das Versenden von Informationen über elektronische Post nicht nur Vorteile mit sich bringt. Wie der Spezialist für Gesundheitsprozessberatung in einem Bericht der Mitteldeutschen Zeitung erwähnt, \glqq macht es die stets wachsende E-Mail-Menge unmöglich sich vernünftig mit den Informationen zu befassen"(vgl. [Ver13]). Nicht nur am Arbeitsplatz sondern auch in Hochschulen wird Gebrauch gemacht, weitere Empfänger oder sogar ganze Verteiler mit in die Kopie einer E-Mail zu integrieren. Um die Prioritäten der Informationen besser bilden zu können, sollen Studierende selbst entscheiden, welche Nachrichten relevant sind. Hierfür wird eine Social Media Plattform mit personalisierbarem Dashboard implementiert.
\section{Ausgangssituation}
Alle Informationen der Fakultät Elektrotechnik Feinwerktechnik Informationstechnik, kurz efi, werden über die globalen Verteiler des Hochschulinternen Postfaches versendet. Viele dieser Daten sind jedoch nur für eine geringe Schnittmenge der Empfänger relevant und lassen sich nur schwer priorisieren. Das ständig überlastete Postfach muss somit regelmä"sig gepflegt werden. Einen massiven Administrativen Aufwand bedeutet es, E-Mails zu filtern und nach persönlichem Ermessen zu verwalten.
Alle Informationen der Fakultät Elektrotechnik Feinwerktechnik Informationstechnik, kurz efi, werden über die globalen Verteiler des Hochschulinternen Postfaches versendet. Viele dieser Daten sind jedoch nur für eine geringe Schnittmenge der Empfänger relevant und lassen sich schwer priorisieren. Das ständig überlastete Postfach muss somit regelmä"sig gepflegt werden. Einen massiven Administrativen Aufwand bedeutet es, E-Mails zu filtern und nach persönlichem Ermessen zu verwalten.
---genauer sagen woher ich mir sicher bin, dass das postfach überlastet ist
Zudem leidet die Nachhaltigkeit der Informationen. Möchten die Empfänger ältere E-Mails abrufen, mussten diese meist schon entfernt werden um Platz für den neuen, eintreffenden E-Mail-Verkehr zu schaffen.
Diese Situation führt dazu, dass Empfänger die Informationen meist nicht lesen und sofort entfernen. Die Ersteller haben keinerlei Möglichkeiten zu überprüfen ob und wie viele Studierende und Dozenten eingehende Nachrichten öffnen und lesen.
Die Empfänger lesen die Informationen meist nicht und entfernen sie sofort. Au"serdem haben die Ersteller keinerlei Möglichkeiten zu überprüfen ob und wie viele Studierende und Dozenten eingehende Nachrichten öffnen und lesen.
Aus diesere Situation heraus, stellt sich folgende Forschungsfrage: \glqq Kann die E-Mail-Flut der Technischen Hochschule mit Hilfe einer Social Media Plattform gedrosselt und die Nachhaltigkeit der Informationen gedrosselt werden?"
\section{Ziel der Arbeit}
Ziel der Arbeit ist es, durch die Einbindung einer Social Media Plattform den Speicheraufwand des Hochschulpostfaches für Studierende der Efi-Fakultät zu reduzieren. Die Flut an E-Mails soll durch das Verwenden eines personalisierte Dashboard gedrosselt werden. Hierbei wird zunächst der Fokus auf die grundlegenden Funktionen der Website gelegt. Dazu gehört das Abonnieren, einpflegen von neuen und löschen von alten Nachrichten.
Zudem sollen die Autoren benachrichtigt werden, in welchem Umfang die hochgeladenen Informationen bereits abonniert und gelesen wurden.
Ziel der Arbeit ist es, durch die Einbindung einer Social Media Plattform den Speicheraufwand des Hochschulpostfaches für Studierende der Efi-Fakultät zu reduzieren. Das Broadcast-Ähnliche senden soll nur noch in den seltensten Fällen von Nöten sein. Um den Administrativen Aufwand der Empfänger so gering wie möglich zu halten, wird das Online-Dashboard individuell konfigurierbar sein. Der Schwerpunkt liegt dabei auf der prototypischen Umsetzung der Website-Erweiterung. Hierbei wird zunächst der Fokus auf die grundlegenden Funktionen dieser gelegt. Dazu gehört das Abonnieren, einpflegen von neuen und löschen von alten Nachrichten. Um den Informationsfluss nicht aus den Augen zu verlieren soll in regelmäßigen Abständen eine automatisierte E-Mail an den Betreffenden versendet werden.
Zudem sollen die Autoren benachrichtigt werden, in welchem Umfang die hochgeladenen Informationen bereits abonniert und gelesen wurden. Dadurch lässt sich, nach einem gewissen Zeitraum, feststellen ob die Studierenden und Dozenten die Nachrichten für relevant erachten und die Plattform weiterhin lohnenswert ist.
@@ -2,3 +2,16 @@
\label{ch:ergebnis}
\subsection{Evaluierung}
Eine weitere hilfreiche Erweiterung ist pylint. Das Tool sucht nicht nur nach Fehlern im Code, sondern versucht diesen sauber und einheitlich zu gestalten. Hierbei wird auf den Code-Standard PEP-8 geprüft [Dix18]. Die folgende Liste zeigt eine Kurzfassung der wichtigsten Regeln:
\begin{itemize}
\item Einrückung, meist 4 Leerzeichen
\item Maximale Zeichenanzahl pro Zeile
\item Zwei Leerzeile zwischen Klassen und Funktionen
\item Eine Leerzeile zwischen Methoden innerhalb einer Klasse
\item Leerzeichen in Ausdrücke und Anweisungen vermeiden
\item Die Reihenfolge der Importe: Standartbibliotheken, Drittanbieterbibliotheken, Lokale Anwendungen
\item Konventionen der Namensgebung von Funktionen, Modulen usw.
\end{itemize}
Natürlich sind dies Vorgaben, die eingehalten werden können, aber nicht notwendig sind um den Code fertig kompilieren und ausgeben zu lassen.
@@ -1,22 +1,21 @@
\chapter{Framework}
\label{ch:framework}
Um die Website-Erweiterung realisieren zu können, wird zunächst festgelegt welche Programmierschnittstellen verwendet werden. Im Web-Backend fällt die Wahl auf die objektorientierte Sprache Python, die ausschlie"slich Serverseitig anwendbar ist. Der Programmaufbau Pythons macht den Code leicht lesbar und der einfache Syntax ermöglicht einen strukturierte Implementierung der Website([Ndu17]). Die vielen abstrakten Datentypen, wie dynamische Arrays und Wörterbücher, sind gro"sflächig einsetzbar.
Ein entscheidender Vorteil hierbei ist das dazugehörige Framework Django, auf das im folgenden Kapitel genauer eingegangen wird.
Um die Website-Erweiterung realisieren zu können, wird zunächst festgelegt welche Programmierschnittstellen verwendet werden. Im Web-Backend fällt die Wahl auf die objektorientierte Sprache Python, die Serverseitig anwendbar ist. Der Programmaufbau Pythons macht den Code leicht lesbar und der einfache Syntax ermöglicht einen strukturierte Implementierung der Website(vgl. [Ndu17]). Durch den modularen Aufbau ist es selbst für unerfahrene Entwickler möglich die Sprache schnell zu erlernen. Darüber hinaus bringt Python verschiedene Web-Service Tools mit sich, die beim implementieren einer Website viel Zeit sparen und das Aneignen von komplexen Protokollen redundant machen (vgl. [Sol17]).
Das dazugehörige Framework Django reduziert den Entwicklungsaufwand eines Prototypen erheblich und ist daher als zielführendes Framework die beste Wahl
\section{Django}
Django ist ein Web-Framework, das auf einer Model-View-Presenter (MVP) Architektur basiert. Ähnlich wie der Model-View-Controller sind die Interaktionen zwischen Model und View die Auswahl und Ausführung von Befehlen und das Auslösen von Ereignissen (vgl. Abbildung 2.1). Da die View aber hier bereits den Gro"steil des Controllers übernimmt, ist der MVP eine Überarbeitung. Der Teil, der Elemente des Modells auswählt, Operationen durchführt und alle Ereignisse kapselt, ergibt die Presenter-Klasse([She09]). Durch die direkte Bindung von Daten und View, geregelt durch den Presenter, wird die Codemenge der Applikation stark reduziert.
Django ist ein Web-Framework, das eine schnelle, strukturierte Entwicklung ermöglicht und dabei ein einfaches Design beibehält. Der darin enthaltene Model-View-Presenter (MVP) kann, ähnlich wie der Model-View-Controller, die Interaktionen zwischen Model und View, die Auswahl und Ausführung von Befehlen und das Auslösen von Ereignissen steuern (vgl. Abbildung 2.1). Da die View aber hier bereits den Gro"steil des Controllers übernimmt, ist der MVP eine Überarbeitung. Der Teil, der Elemente des Modells auswählt, Operationen durchführt und alle Ereignisse kapselt, ergibt die Presenter-Klasse vgl. [She09]. Durch die direkte Bindung von Daten und View, geregelt durch den Presenter, wird die Codemenge der Applikation stark reduziert.
\begin{figure}[!h]
\centering
\includegraphics[width=0.5\textwidth]{figures/MVP}
\caption{Vereinfachter MVP}
\includegraphics[width=0.6\textwidth]{figures/MVP}
\caption{Vereinfachter MVP ([She09])}
\hfill
\end{figure}
Der Prozess vom Anfragen der URL über den Server, bis hin zur fertig gerenderten Website kann wie folgt vereinfacht darstellen. Der User gibt eine URL im Browser ein und sendet sie an den Web-Server.
Der Prozess vom Anfragen der URL über den Server, bis hin zur fertig gerenderten Website kann wie folgt vereinfacht dargestellt werden.
Das Interface WSGI am Web-Server verbindet diesen mit dem Web-Framework, indem es den Request zum passenden Objekt weiterleitet. Hier wird der Applikation eine Callback-Funktion zur Verfügung gestellt [Kin17]. Au"serdem werden folgende Schritte durchgeführt:
Der User gibt eine URL im Browser ein und sendet sie an den Web-Server. Das Interface WSGI am Web-Server verbindet diesen mit dem Web-Framework, indem es den Request zum passenden Objekt weiterleitet. Hier wird der Applikation eine Callback-Funktion zur Verfügung gestellt (vgl. [Kin17]). Au"serdem werden folgende Schritte durchgeführt:
\begin{itemize}
\item Die Middleware-Klassen aus der settings.py werden geladen
\item Die Methoden der Listen Request, View, Response und Excpetion werden geladen
@@ -24,42 +23,37 @@ Das Interface WSGI am Web-Server verbindet diesen mit dem Web-Framework, indem e
\end{itemize}
Der WSGI-Handler fungiert also als Pförtner und Manager zwischen dem Web-Server und dem Django-Projekt.
Um die URL, wie weiter oben erwähnt, aufzulösen, benötigt WSGI einen urlresolver. Durch die explizite Zuweisung der vorhandenen Seiten, kann dieser über die regulären Ausdrücke der url.py-Datei iterieren. Gibt es eine Übereinstimmung, wird die damit verknüpfte Funktion in der View (view.py) aufgerufen. Hier ist die gesamte Logik der Website lokalisiert. Wie bereits erwähnt, ist es möglich unter Anderem auf die Datenbank der Applikation zuzugreifen und Eingaben des Users über eine Form zu verarbeiten. Nachdem werden die Informationen der View an das Template weitergereicht. Es handelt sich dabei um eine einfache HTML-Seite in der der strukturelle Aufbau im Frontend festgelegt wird. Die Informationen der View können hier zwischen doppelt-geschweiften Klammern eingebunden und, wenn nötig, mit einfachen Python-Befehlen angepasst werden. Nun kann das Template, die vom WSGI-Framework zur Verfügung gestellte Callback-Funktion befüllen und einen Response an den Web-Server schicken. Die fertige Seite ist beim Klienten im Browserfenster zum rendern bereit (vgl. Abbildung 2.2.).
Um die URL, wie weiter oben erwähnt, aufzulösen, benötigt WSGI einen \textit {urlresolver}(vgl. ). Durch die explizite Zuweisung der vorhandenen Seiten, kann dieser über die regulären Ausdrücke der url.py-Datei iterieren. Gibt es eine Übereinstimmung, wird die damit verknüpfte Funktion in der View (view.py) aufgerufen. Hier ist die gesamte Logik der Website lokalisiert. Es ist möglich unter Anderem auf die Datenbank der Applikation zuzugreifen und Eingaben des Users über eine Form zu verarbeiten. Nachdem werden die Informationen der View an das Template weitergereicht. Es handelt sich dabei um eine einfache HTML-Seite in der der strukturelle Aufbau im Frontend festgelegt wird. Die Informationen der View können hier zwischen doppelt-geschweiften Klammern eingebunden und, wenn nötig, mit einfachen Python-Befehlen angepasst werden. Nun kann das Template, die vom WSGI-Framework zur Verfügung gestellte Callback-Funktion befüllen und einen Response an den Web-Server schicken. Die fertige Seite ist beim Klienten im Browserfenster zum rendern bereit (vgl. [Kin17], Abbildung 2.2.).
\begin{figure}[!h]
\centering
\includegraphics[width=0.5\textwidth]{figures/request-response-cycle}
\caption{Request-Response-Kreislauf des Django Frameworks}
\caption{Request-Response-Kreislauf des Django Frameworks ([Nev15])}
\hfill
\end{figure}
\subsection {Besonderheiten}
\subsection {Besonderheiten Django's}
Das Django-Framework bringt einige Besonderheiten mit sich, die beim implementiern des Prototypen von Bedeutung sind. Diese werden im Folgenden beschrieben.
Die Administratoroberfläche ist eines der hilfreichsten Werkzeugen des gesamten Frameworks. Es stellt die Metadaten der Modelle aus dem Code visuell dar. Verifizierte Benutzer können die Daten nicht nur schnell erfassen, sondern diese auch editieren und verwalten. Das Recht, das Admin-Backend uneingeschränkt zu benutzen, ist dem sogenannten superuser vorenthalten. Dieser kann beim erstmaligen zuweisen nur über die Kommandozeile eingerichtet werden. Ist bereits ein superuser vorhanden, kann dieser im Admin-Backend weiteren Benutzern den gleichen Handlungsfreiraum einräumen. Zudem gibt es noch weitere Stufen der Zugangsberechtigungen, Staff- und Active-Status, die für eine breitere Gruppe von Benutzern geeignet ist.
Um die gestaffelten Zugangsberechtigungen auch auf der Website umsetzen zu können, stellt Django verschiedene Decorator zur Verfügung. Soll eine bestimmte Seite nur von eingeloggten Benutzern besucht werden dürfen, so importiert man die Decorator des, von Django zur Verfügung gestellten, Authentifizierungssystems mit
Um die gestaffelten Zugangsberechtigungen auch auf der Website umsetzen zu können, stellt Django verschiedene Decorator zur Verfügung. Soll eine bestimmte Seite nur von eingeloggten Benutzern besucht werden, so importiert man die Decorator des, von Django zur Verfügung gestellten, Authentifizierungssystems mit
\\
\noindent\hspace*{10mm}%
from django.contrib.auth.decorators import login\_required
\\
Direkt über den Beginn der Funktion in view.py, oder auch single-view-function, wird zudem folgende Zeile ergänzt:
Vor der Definition der Funktion wird dann folgende Zeile ergänzt:
\\
\noindent\hspace*{10mm}%
\makeatletter @login\_required
\\
Natürlich lassen sich die Decoratoren auch für andere Zwecke vor Funktionen platzieren. Begrenzte Zugänge zu den Views können durch die Art der Anfrage realisiert werden. Der Benutzer muss also zum Beispiel durch GET auf eine Seite zugreifen wollen um Einsicht zu erhalten.
Natürlich lassen sich Decorator auch für andere Zwecke vor Funktionen platzieren. Unter Anderem werden so die Views vor möglichen Angriffen, wie Cross-Site-Scripting, geschützt.
Benutzerdefinierte Decorator sind im Django-Framework möglich, darauf wird hier aber nicht weiter eingegangen.
Durch den einfachen Aufbau ist es au"serdem möglich diese selbst zu implementieren. Ein einfaches Beispiel wäre das prüfen des, an die Funktion übergebenen, Parameter. Sollen nur positive Zahlen verarbeitet werden, so kann der Decorator alle anderen Eingaben abfangen.
\subsection{Virtuelle Umgebung}
\section{Erweiterungen}
Die Programmiersprache Python bringt viele hilfreiche Erweiterungen mit sich. Die im folgenden aufgeführten sind hauptsächlich für die Paketverwaltung der Applikation zuständig.
Wird ein neues Projekt gestartet, ist es üblich, verschiedensten Programme und Module dafür zu verwenden. Die Versionen dieser spielen hierbei eine entscheidende Rolle, um Konflikte zu vermeiden [Fou18]. Um diesem Problem vorzubeugen, wird eine virtuelle Umgebung implementiert. Diese besitzt einen eigenen Projektpfad, beinhaltet alle nötigen Pakete und Bibliotheken, und lässt sich nach dem Einrichten im Terminal benutzten. Die folgende Abbildung (2.3) zeigt das Erstellen eines neunen Ordners, das Erstellen der virtuellen Umgebung und den Aktivierungsbefehl. Ist der Name des Environment in Klammern am Anfang der Zeile, bedeutet das, diese ist jetzt aktiv.
Wird eine prototypische Anwendung gestartet, ist es notwendig, verschiedensten Module zu verwenden und zu testen. Die Versionen dieser spielen hierbei eine entscheidende Rolle, um Konflikte zu vermeiden [Fou18]. Um diesem Problem vorzubeugen, wird eine virtuelle Umgebung implementiert. Diese besitzt einen eigenen Projektpfad, beinhaltet alle nötigen Pakete und Bibliotheken, und lässt sich nach dem Einrichten im Terminal starten. Die folgende Abbildung (2.4) zeigt das Erstellen eines neuen Ordners, das Erstellen der virtuellen Umgebung und den Aktivierungsbefehl. Ist der Name des Environment in Klammern am Kommandozeilenanfang, bedeutet das, diese ist jetzt aktiv.
\begin{figure}[!h]
\centering
@@ -68,32 +62,24 @@ Wird ein neues Projekt gestartet, ist es üblich, verschiedensten Programme und
\hfill
\end{figure}
Die Umgebung ist jetzt bereit befüllt zu werden. Um die Pakete und Module kollisionsfrei zu installieren ist es empfehlenswert einen Package-Manager zu verwenden. Mit pip können diese installiert, geupdated und gelöscht werden. Au"serdem kann der Manager Abhängigkeiten, wenn nötig, überschreiben und optimieren. Falls ein, sich von der neuesten Version unterscheidendes, Programm installiert werden soll, so ist dies ebenso möglich.
Um die Pakete und Module kollisionsfrei zu installieren ist es empfehlenswert einen Package-Manager zu verwenden. Mit pip, \glqq rekursives Akronym für pip Install Packages" (vgl. [Wei17, K. 23.1]), können diese installiert, geupdated und gelöscht werden. Au"serdem kann der Manager Abhängigkeiten, wenn nötig, überschreiben und optimieren. Falls ein, sich von der neuesten Version unterscheidendes, Programm installiert werden soll, so ist dies ebenso möglich.
Eine weitere hilfreiche Erweiterung ist pylint. Das Tool sucht nicht nur nach Fehlern im Code, sondern versucht diesen sauber und einheitlich zu gestalten. Hierbei wird auf den Code-Standard PEP-8 geprüft [Dix18]. Die folgende Liste zeigt eine Kurzfassung der wichtigsten Regeln:
\subsection{Lightweight Directory Access Protocol}
\begin{itemize}
\item Einrückung, meist 4 Leerzeichen
\item Maximale Zeichenanzahl pro Zeile
\item Zwei Leerzeile zwischen Klassen und Funktionen
\item Eine Leerzeile zwischen Methoden innerhalb einer Klasse
\item Leerzeichen in Ausdrücke und Anweisungen vermeiden
\item Die Reihenfolge der Importe: Standartbibliotheken, Drittanbieterbibliotheken, Lokale Anwendungen
\item Konventionen der Namensgebung von Funktionen, Modulen usw.
\end{itemize}
Natürlich sind dies Vorgaben, die eingehalten werden können, aber nicht notwendig sind um den Code fertig kompilieren und ausgeben zu lassen.
Das ldap, Lightweight Directory Access Protocol, muss als Erweiterung in die hier bearbeitende Bachelor-Arbeit eingebunden werden. Dies ist ein Internetprotokoll, welches die Kommunikation mit dem Hochschulinternen Active Directory verwaltet. Es wird eingesetzt um Benutzer so schnell und effizient wie möglich durch eine bereits existierende Datenbank abzufragen und zu authentifizieren. Der Aufbau ist mit einem Baum zu vergleichen (vgl. Abbildung 2.4.). Die Wurzel besteht aus sehr allgemeinen Informationen, umso näher man den Blättern kommt, umso spezifischer werden diese. Ein Objekt in der Struktur wird durch einen einmaligen Namen identifiziert, der aus den gesamten hinterlegten Informationen besteht. Der Name für den in der Abbildung 2.4 dargestellten Baum wäre \glqq cn=John Doe, ou=People, dc=sun.com\grqq.
Das ldap, Lightweight Directory Access Protocol, muss als Erweiterung in die hier bearbeitende Bachelor-Arbeit eingebunden werden, um später die Login-Daten im Hochschulinternen Netz abfragen zu können. Dies ist ein Internetprotokoll, welches die Kommunikation mit dem Active Directory verwaltet. Es wird eingesetzt um Benutzer so schnell und effizient wie möglich durch eine bereits existierende Datenbank abzufragen und zu authentifizieren. Der Aufbau ist mit einem Baum zu vergleichen (vgl. Abbildung 2.4.). Die Wurzel besteht aus sehr allgemeinen Informationen, umso näher man den Blättern kommt, umso spezifischer werden diese. Ein Objekt in der Struktur wird durch einen einmaligen Namen identifiziert, der aus den gesamten hinterlegten Informationen besteht. Der Name für den in der Abbildung 2.4 dargestellten Baum wäre \glqq cn=John Doe, ou=People, dc=sun.com\grqq (vgl. [Sch17]).
\begin{figure}[!h]
\centering
\includegraphics[width=0.5\textwidth]{figures/ldap-tree}
\caption{Beispiel eines LDAP-Trees}
\caption{Beispiel eines LDAP-Trees ([Orc10])}
\hfill
\end{figure}
\section {Erweiterungen}
Django bringt viele hilfreiche Erweiterungen mit sich, die mit einem Packagemanager einfach in die virtuelle Umgebung geladen werden können. Um das passende Add-on für ein Projekt zu finden, bietet die Plattform djangopackages.org alle Erweiterungen in übersichtlichen Tabellen mit Eigenschaften und Bewertung an.
\subsection {Taggable-Manager}
Django-taggit ist eine Erweiterung von Alex Gaynor, einem Entwickler aus Washingtion DC. Das Add-on ermöglicht das Verwenden von Tags die automatisch mit einem eindeutigen Slug versehen werden. Der darin enthaltene Taggable Manager verwendet Django's Contenttype Framework, welches per Default verwendet wird, um die Modelle der Applikation zu verfolgen und diese durch generische Beziehungen zu verknüpfen. Die Felder app\_label und model machen die Modelle eindeutig zuweisbar. Instanzen des Contenttypes präsentieren und speichern die Informationen und Erstellen automatisch neue Instanzen, wenn Modelle hinzugefügt werden. Zudem stehen Methoden zur Verfügung, die das Abrufen und Arbeiten mit Instanzen der einzelnen Modelle erleichtern.
Django-taggit ist eine Erweiterung, die das Verwenden von Tags vereinfacht. Der darin enthaltene Taggable Manager verwendet Django's Contenttype Framework, welches per Default verwendet wird, um die Modelle der Applikation zu verfolgen und diese durch generische Beziehungen zu verknüpfen. Die Felder app\_label und model machen die Modelle eindeutig zuweisbar. Instanzen des Contenttypes präsentieren und speichern die Informationen und Erstellen automatisch neue Instanzen, wenn Modelle hinzugefügt werden. Zudem stehen Methoden zur Verfügung, die das Abrufen und Arbeiten mit Instanzen der einzelnen Modelle erleichtern.
Der Taggable-Manager ist jedoch nicht an das Contenttype-Framework gebunden ([Her16]). Durch die Verwendung eines echten Fremdschlüssels, kann zum Beispiel die Performance und Referenzgarantie verwirklicht werden. Dazu ist lediglich die Erstellung einer eigenen lookup-Tabelle notwendig, die die Entitäten zweier Tabellen direkt verlinkt, anstatt diese generische zu verbinden. Weiterführend können Modelle in einem benutzerdefinierten Modell vereint werden, sodass er Zugriff auf diese einheitlich geschieht. Außerdem ist es möglich Primary-Keys zu verwenden, die nicht aus ganzen Zahlen bestehen, sondern aus Buchstaben und Wörtern.
@@ -103,22 +89,22 @@ Um django-taggit zu installieren wird der folgende Befehl in die Kommandozeile e
\$ pip install django-taggit
\\
Im model.py wird das Feld tag neu erstellt und als Taggable Manager definiert.
Au"serdem muss taggit in der settings.py unter INSTALLED\_APPS ergänzt werden. Um dem Programm zu sagen, dass nun eine neue Liste der Datenbank hinzugefügt werden muss, werden folgende Befehle in die Kommandozeile eingefügt:
\\
\noindent\hspace*{10mm}%
\$ python3 manage.py makemigrations \\
\noindent\hspace*{10mm}%
\$ python3 manage.py migrate
\\
Au"serdem muss taggit in der settings.py unter INSTALLED\_APPS ergänzt werden. Um dem Programm mitzuteilen, dass nun eine neue Liste der Datenbank hinzugefügt werden muss, werden über die Kommandozeile Migrations-Befehle ausgeführt, die im Kapitel Datenmodellierung genauer beschreiben werden.
Im Admin-Backend kann nun geprüft werden, ob das neue Feld in die Datenbank integriert wurde. Neue Tags können in das Textfeld eingetragen werden. Der Parser verarbeitet jedes Wort, dass durch ein Komma oder ein Leerzeichen getrennt ist als ein Tag. Soll dieses jedoch aus mehreren Wörtern bestehen so müssen diese mit Anführungszeichen umfasst werden. Standardmäßig unterscheidet der Taggable Manager zwischen Groß- und Kleinschreibung, Tags sind also case sensitive. Ändern kann man das, indem der Boolean TAGGIT\_CASE\_INSENSITIVE in der settings.py auf True gestellt wird.
\subsection {Hilfsbibliotheken}
---warum habe ich diese bibs gealden (beschreibung evtl in prototyp)
Weitere Add-ons werden geladen um kleinere Funktionen der Website einfach umsetzen zu können. Zu diesen gehört django-taggit-templatetags, welches durch die Einbindung im HTML-File die Tags der Applikation als Liste ausgibt. Au"serdem lassen sich die eingepflegten Tags als Cloud visualisieren. Kommen bestimmte Tags öfters vor als andere, so werden sie entsprechend grö"ser dargestellt.
Django-hitcount dient zum zählen der Besucher einer Seite ([Tim15]). Dies lässt sich auf drei verschiedene Arten in die Applikation einbinden. Der schnellste Weg ist die Darstellung der Besuche mit Hilfe eines Template Tags im HTML-File. Möchte man die Anzeige aber individueller gestalten so kann durch das integrieren der HitCountDetailView in views.py die Variable hitcount verwenden und im Frontend ausgeben. Eine weiter Möglichkeit ist das Erweitern oder neu Erstellen eines Models im Django Backend. Von dort kann auf das neue Feld im Django-Admin-Backend zugegriffen werden, ebenso wie in der View und im Template. Die im Add-on integrierten Einstellungen, die in der settings.py ergänzt werden müssen, ermöglichen unter Anderem das begrenzen der Lebensdauer des Zählers, bevor dieser zurück gesetzt wird.
Um das Versenden und Verwalten von E-Mails in Django zu realisieren eignet sich django-post-office ([Ong18]). Nach der Installation kann im Admin-Backend ein E-Mail-Templates angelegt werden, Anhänge verwaltet und das Senden dieser im Log überprüft werden. Die Benachrichtigungen können asynchron versendet werden mit einem integrierten Planungsmanager. Der Inhalt kann Text oder HTML-basiert sein und in mehreren Sprachen hinterlegt werden.
\section{Bootstrap}
Eine umfangreiche Website einheitlich zu gestalten ist oft sehr komplex und zeitaufwendig. Die Entwickler von Twitter haben deshalb, zunächst Firmenintern, an einem neuen Verwaltungswerkzeug gearbeitet, das mehrere Bibliotheken zusammenführen sollte. Sie merkten, das die neue Bibliothek, die daraus entstand, nicht nur auf Ihre eigene Website anwendbar, sondern so flexible ist, dass jede Art von Website davon profitieren könnte. 2011 entschieden Sie sich Bootstrap für die Öffentlichkeit frei zugänglich zu machen. Die Open-Source-Bibliothek, die auf GitHub abrufbar ist, wird seitdem von vielen interessierten Programmierern weiterentwickelt und ist somit stark gewachsen. Version 2.0 verfügt au"serdem über die Fähigkeit Websites responsive auf verschiedenste mobile Endgeräte anzupassen.
Eine umfangreiche Website einheitlich zu gestalten ist oft sehr komplex und zeitaufwendig. Die Entwickler von Twitter haben deshalb, zunächst Firmenintern, an einem neuen Verwaltungswerkzeug gearbeitet, das mehrere Bibliotheken zusammenführen sollte. Sie merkten, das die neue Bibliothek, die daraus entstand, nicht nur auf Ihre eigene Website anwendbar, sondern so flexible ist, dass jede Art von Website davon profitieren könnte (vgl. [Ott11]). Die Open-Source-Bibliothek, die auf GitHub abrufbar ist, wird seitdem von vielen Programmierern weiterentwickelt und ist somit stark gewachsen. Version 2.0 verfügt au"serdem über die Fähigkeit Websites responsive auf verschiedenste mobile Endgeräte anzupassen (vgl. [Ott12]).
Das Bootstrap-Paket beinhaltet vorgefertigte Cascading Stylesheets, kurz CSS, die Farben, Schriftarten und viele weitere Stildefinitionen. Zudem befinden sich auch Erweiterungen des JavaScript-Frameworks jQuery in der Bibliothek, die weiterführende Funktionen beinhalten wie zum Beispiel Filter oder Dropdown-Menüs. Das Paket kann einfach eingebunden werden im head-tag einer HTML-Datei (vgl. Abbildung 2.3). Das bedeutet, dass Medie-Queries oder ähnliche Methoden nicht mehr nötig sind, nicht nur um eine Website mobilfähig zu machen, sondern auch kompatibel für die verschiedensten Browser.
Das Bootstrap-Paket beinhaltet vorgefertigte Cascading Stylesheets, kurz CSS, die Farben, Schriftarten und viele weitere Stildefinitionen. Zudem befinden sich auch Erweiterungen des JavaScript-Frameworks jQuery in der Bibliothek, die weiterführende Funktionen beinhalten wie zum Beispiel Filter oder Dropdown-Menüs. Das Paket kann einfach eingebunden werden im head-tag einer HTML-Datei (vgl. Abbildung 2.3). Das bedeutet, dass Media-Queries oder ähnliche Methoden nicht mehr nötig sind, nicht nur um eine Website mobilfähig zu machen, sondern auch kompatibel für die verschiedensten Browser (vgl. [Boo12]).
\begin{figure}[!h]
\centering
@@ -127,7 +113,7 @@ Das Bootstrap-Paket beinhaltet vorgefertigte Cascading Stylesheets, kurz CSS, di
\hfill
\end{figure}
Durch das Einbinden von Bootstrap in einer HTML-Datei werden einige Styles bereits automatisch auf die darin vorkommenden Tags, wie Links und Überschriften, angewandt. Dies ist jedoch nur ein sehr kleiner Teil den die Bibliothek zur Verfügung stellt. Möchte man Bootstrap umfangreich nutzen so lassen sich die Stildefinitionen mit Klassen oder ID's in diverse HTML-Tags eintragen (vlg. Abbildung 2.4.).
Durch das Einbinden von Bootstrap in einer HTML-Datei werden einige Styles bereits automatisch auf die darin vorkommenden Tags, wie Links und Überschriften, angewendet. Dies ist jedoch nur ein sehr kleiner Teil den die Bibliothek zur Verfügung stellt. Möchte man Bootstrap umfangreich nutzen so lassen sich die Stildefinitionen mit Klassen oder ID's in diverse HTML-Tags eintragen (vlg. Abbildung 2.4.).
\begin{figure}[!h]
\centering
@@ -140,5 +126,5 @@ Möchte man bestimmte gestalterische Eigenschaften von Bootstrap überschreiben
\section{Cron}
@@ -1,19 +1,115 @@
\chapter{Prototyp}
\label{ch:prototyp}
Um zu beweisen, wie eine Spcial Media Plattform die Mail-Flut der Efi-Fakultät reduzieren kann, wird ein Prototyp implementiert.
Um die wissenschaftliche Frage, nicht nur zu beantworten, sondern zu beweisen, wird in dieser Arbeit die Methode des Prototypings genutzt. Der Prototyp dient zum experimentellen Arbeiten und sichert eine strukturell fundierte Umsetzung des darauf folgenden Endprodukts. Der Fokus liegt dabei zunächst auf der Funktionalität der Anwendung. Prototyping wird als bevorzugte Methode gewählt um schnell ein Ergebnis zu erzielen (vgl. [Abr16]). Zudem soll aufbauend auf Diesem ein Produkt realisiert werden, das als Erweiterung in das Netzwerk der Hochschule integriert werden soll.
\section{Forschungsdesign}
Das Kapitel zeigt eine kurze Übersicht der Vorgehensweise und den Leitfaden an den sich die Implementierung des Prototyps anlehnt (vgl. Abbildung 3.1.).
Zu Beginn der Arbeit wird, des sich aus der Forschungsfrage ergebenden Problems analysiert und alle wichtigen Anforderungen erfasst. Dies bildet die Basis für alle weitern notwendigen Schritte um am Ende eine sinnvolle Lösung bereitstellen zu können. Die Recherche dient der Sammlung aller notwendigen Werkzeuge und gibt einen Überblick über verschiedene Hilfsbibliotheken. Das Implementieren der Applikation kann nun auf Basis der Recherche durchgeführt werden. Dazu gehört das Testen verschiedener Bibliotheken und Erweiterungen um die bestmögliche Ergebnis zu eruieren. Abschlie"send wird die Funktionalität des Prototypen getestet und evaluiert ob die Forschungsfrage ausreichend beantwortet wird. Handlungsempfehlungen und mögliche Funktionen zum Erweitern finalisieren die Arbeit.
\begin{figure}[!h]
\centering
\includegraphics[width=0.8\textwidth]{figures/forschungsdesign}
\caption{Forschungsdesign}
\hfill
\end{figure}
\section{Organisation}
Grundlegender Aufbau der Website, Verwaltung der Daten evlt auf nochmal Taggable-Manager (ManyToMany) ...
\subsection{Verwaltung im Administrator-Backend}
Näher auf CustomUserModel eingehen, Diagramm erstellen und einbinden
Zum Entwickeln des Prototypen wird die Open-Source- Entwicklungsumgebung Visual Studio Code von Microsoft verwendet. Die dynamische Oberfläche ist sehr performant, da sie wenig Speicherplatz bedarf und zunächst ohne grö"sere Ergänzungen sofort einsetzbar ist. Um den Code mit Shortcuts übersichtlicher zu gestalten und auf Python und Django abstimmen zu können werden aus der üppigen \textit{Extension-Bibliothek} kostenlose Erweiterungen eingebunden. Unter Anderem sind die folgenden Add-on's während der Arbeit zum Einsatz gekommen (vgl. [Mic18]):
\begin{itemize}
\item Python von Microsoft (Linting und Debugging)
\item MagicPython von MagicStack Inc. (Syntax-Highlighter)
\item Python Extension Pack (Django Code-Vervollständigung)
\end{itemize}
Der entwickelte Syntax wird regelmä"sig auf das hochschulinterne Git-Repository geladen. Hier kann man mit Hilfe der verwendeten IDE bei der Überarbeitung im sogenannten \textit {Working-Tree} die Änderungen visuell einsehen und wenn nötig bearbeiten. Es wird ausschlie"slich auf den Master-Branch gepusht, da nur ein Entwickler an dem Prototyp tätig ist. Lediglich zum Testen experimenteller Bibliotheken werden neue Branches angelegt und diese, falls Sie sich als hilfreich erweisen, mit dem Master-Branch fusionieren.
Um einen Einblick in den Aufbau eines Django-Projektes zu erlangen wird dies im folgenden genauer beschrieben. Die unterste Projektebene wird durch die manage.py-Datei gebildet. Sie wird unter Anderem genutzt um den lokalen Server starten zu können. In der Ebene darüber findet sich im Ordner \glqq mysite\grqq\ die Datei settings.py. Hier werden die allgemeinen Einstellungen der Website vorgenommen, wie zum Beispiel das Integrieren der Erweiterungen und der Pfad zu den hinterlegten Templates. Au"serdem ist die urls.py dort zu finden, deren Funktion bereits im Kapitel Django erläutert wurde. Die \_\_init\_\_.py und wsgi.py sind trivial und deshalb wird hier nicht weiter darauf eingegangen. Im Ordner \glqq thisisenv\grqq\ sind alle Bibliotheken und Add-on's der virtuellen Umgebung hinterlegt. Der Fokus dieser Arbeit liegt im Ordner \glqq application\grqq. Hier sind die Datenbank-Migrationen, die Static-Files wie bootstrap und css, und alle Templates abgelegt. Zudem befindet sich hier die Logik des Prototypen, auf die im Kapitel \glqq Funktionen\grqq\ weiter eingegangen wird.
\subsection{Datenmodellierung}
Die Struktur der bereits bestehenden Datenbank im Django-Framework und die Erweiterungen dessen werden hier genauer erläutert. Zunächst wird auf die Ergänzung des bestehenden \textit {UserModel} eingegangen, nachdem veranschaulicht der Abschnitt das \textit {PostModel} und abschlie"send werden die Zusammenhänge der Modelle dargestellt.
Alle Modelle werden als Django-Modelle deklariert um beim kompilieren des Codes dem Compiler mitzuteilen, dass diese integriert werden müssen (vgl. [Dja18]). Mit der folgenden Eingabe
\\
\noindent\hspace*{10mm}%
\$ python3 manage.py makemigrations
\\
werden die neun Tabellen der Modelle erstellt. Um diese dann auch anwenden zu können, muss der Befehl
\\
\noindent\hspace*{10mm}%
\$ python3 manage.py migrate
\\
darauffolgend ebenso in die Kommandozeile eingegeben werden.\\
\textbf{UserModel:}
\begin{addmargin}[25pt]{0pt}
Hierbei ist das Authentifizierungssystem von Django mit einem \textit{UserModel} bereits angelegt. Dies muss für den Prototyp um das Feld \glqq tags \grqq\ erweitert werden, sodass ein Benutzer folgende Felder aufweist (vgl. [Fou18a]):
\begin{itemize}
\item username, fist\_name, last\_name, email, groups, user\_permissions, is\_staff, is\_active, is\_superuser, last\_login, date\_joined, tags
\end{itemize}
Das Feld \textit{groups} wird in dieser Arbeit nicht verwendet und deshalb im Folgenden ignoriert.
In models.py ist der \textit{CustomUser} dafür verantwortlich das neue Feld mit dem \textit{Default-User} zu verknüpfen. Durch das \textit{OneToOneField} (siehe Abbildung 3.2.) wird die Verbindung zum schon bestehenden Modell hergestellt. \textit{OneToOne} bildet eine einzigartige Zuordnung von zwei Objekten, sodass der Rückgabewert nur aus einem Objekt besteht (vgl. [Fou18a]). Das hei"st, dass hier keine Rekursiven, also auf sich selbst verlinkende oder \textit{lazy} Beziehungen möglich sind um Konflikte bei der Authentifizierung zu vermeiden. Dies ist die übliche Vorgehensweise um mit einem Primärschlüssel das Default-Model zu erweitern.
\begin{figure}[!h]
\centering
\includegraphics[width=1\textwidth]{figures/custommodelcode}
\caption{CustomUserModel in models.py}
\hfill
\end{figure}
\end{addmargin}
\textbf{PostModel:}
\begin{addmargin}[25pt]{0pt}
Das \textit{PostModel} beschreibt alle Felder die ein Post enthalten kann. Basierend auf der Blog-Lösung von Djangogirls.com gehören dazu folgende:
\begin{itemize}
\item author, title, text, created\_date, published\_date, tags
\end{itemize}
Der Autor ist durch einen \textit{ForeignKey} mit dem \textit{UserModel} verbunden. Diese sogenannte \textit{ManyToOne} Verbindung reicht hier aus um einem Post den Autor, also dem eingeloggten User, zuzuweisen. Title ist ein \textit{CharField} und wird mit einer Zeichenbegrenzung festgelegt. Der Text hingegen kann eine beliebige Menge an Zeichen enthalten und wir deshalb als \textit{TextField} deklariert. Erstellungsdatum und Publikation sind beides \textit{DateTimeField}s. Ersteres muss vom Ersteller angegeben werden, Zweiteres kann zunächst offen gelassen werden durch die Zusatzangabe \glqq null=True\grqq. Ein weiteres Feld tags wird hinzugefügt um den Posts unabhängig von den Usern Tags zuordnen zu können.
\\
\end{addmargin}
\textbf{Gesamtmodellierung:}
\begin{addmargin}[25pt]{0pt}
Die Abbildung 3.3. zeigt die Modellierung der Tabelle \glqq User\grqq\ und \glqq Post\grqq. Au"serdem verdeutlicht es die Erweiterung des User-Modells von Django mit dem in der Applikation angelegtem CustomUser. Die im User vorkommenden booleschen Felder werden im Kapitel Berechtigung der User genauer erörtert.
\begin{figure}[!h]
\centering
\includegraphics[width=0.9\textwidth]{figures/datamodel}
\caption{Datenmodellierung von User und Post}
\hfill
\end{figure}
\end{addmargin}
\subsection{Verwaltung im Administrator-Back-end}
In diesem Kapitel wird beschrieben wie das Administrations-back-end genutzt werden kann. Es ist jedoch zu beachten, dass die Applikation vorwiegend von Dozenten und Angestellten der Hochschule ohne Administratorrechte verwendet werden soll. Die gestaffelten Berechtigungen werden im Kapitel \glqq Berechtigung der User \grqq\ genauer beschreiben.
Ein Django-Projekt bildet bereits beim Einrichten, \textit{per Default}, eine Administrator-Oberfläche um die Inhalte der Website kontrollieren zu können. Nach der Migration von den oben genannten Modellen wird diese erweitert. Nich zu vergessen sind die externen Tabellen der installierten Add-on's, die nach der Migration das Back-end expandieren.
\subsection{Berechtigung der User}
Welche Berechtigungen gibt es im Prototyp, welche werden vom Active Directory übernommen?
Im Allgemeinen verwendet man Berechtigungen um Benutzern Zugang zu bestimmten Resourcen in einem Netzwerk einzuräumen. Au"serdem bestimmt es die Arte des Zugangs, also ob der User die Resourcen nur lesen oder auch verändern oder löschen darf(vgl. [Com18]). Die Rechte werden meist einzelnen Individuen oder einer Gruppe zugeordnet.
Das gestaffeltes Berechtigungsmanagement ist im Prototyp notwendig um den Umgang mit Informationen so sicher wie möglich zu gestalten und um die Nachhaltigkeit dieser zu bewahren. Des Weiteren soll der Prototyp als Vorlage für die Erweiterung der Hochschulwebsite dienen und daher ist eine ähnliche Verteilung der Zugangsberechtigungen sinnvoll.
Studenten sollen zunächst Informationen weder einpflegen, noch editieren dürfen. Die einzigen Änderungen die sie vornehmen können sind auf Ihre eigene Datenbank fokussiert. Das Hinzufügen von Tags um die damit verbunden Posts auf dem persönlichen Dashboard zu sehen wird ihnen gewährleistet. Dies soll verhindern, dass Informationen nicht zu leichtfertig geändert oder gelöscht werden.
Dozenten und Angestellte der Hochschule sind dazu berechtigt, Posts zu erstellen, zu editieren und wieder zu löschen. Zudem können sie, wie Studenten, Tags abonnieren und ebenso das persönliche Dashboard gestalten. Das Einloggen in die Administratoroberfläche kann vorgenommen werden, jedoch sind der Gruppe noch keinerlei Rechte zugewiesen. Möchte man dies ändern, kann man das von Django bereitgestellte Feld \glqq User Permissions\grqq\ im Admin-back-end unter Users, und dem Namen der zu ändernden Person, die gewünschte Berechtigung erteilen.
\section{Funktion}
Nötige Funktionen
\section{Funktionen}
User Stories einbinden als Grafik
\subsection{Verwalten}
Posts erstellen, editieren und löschen
(draft-list und post new für Mitarbeiter)
\subsection{Abonnieren}
Tags als eingeloggter User abonnieren und verwalten Front-end und Admin-Backend?
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

@@ -53,3 +53,111 @@
year = "2018",
note = "https://www.datacamp.com/community/tutorials/pep8-tutorial-python-code"
}
@article{Shabda,
author = "Shabda",
title = "Understanding decorators",
year = "2009",
note = "https://www.agiliq.com/blog/2009/06/understanding-decorators/"
}
@article{Timm,
author = "Damon Timm",
title = "django-hitcount Documentation",
year = "2015",
note = "https://django-hitcount.readthedocs.io/en/latest/overview.html"
}
@article{Ong,
author = "Selwin Ong",
title = "django-post\_office git Repository",
year = "2018",
note = "https://github.com/ui/django-post\_office/blob/master/AUTHORS.rst"
}
@article{Ong,
author = "Ryan Nevius",
title = "django-post\_office git Repository",
year = "2015",
note = "https://ryannevius.com"
}
@article{Solutions,
author = "Mindfire Solutions",
title = "Advantages and Disadvantages of Python Programming Language",
year = "2017",
note = "https://medium.com/@mindfiresolutions.usa/advantages-and-disadvantages-of-python-programming-language-fd0b394f2121"
}
@book{Weigend,
author = "Michael Weigend",
title = "Python GE-PACKT",
year = "2017",
note = "Kapitel 23.1"
}
@article{Schlede,
author = "Frank-Michael Schlede, Thomas Bär, Andreas Donner",
title = "Was ist LDAP (Lightweight Directory Access Protocol)?",
year = "2017",
note = "https://www.ip-insider.de/was-ist-ldap-lightweight-directory-access-protocol-a-581204/"
}
@article{Oracle,
author = "Oracle Cooperation",
title = "About LDAP",
year = "2010",
note = "https://docs.oracle.com/cd/E19182-01/820-6573/6nht2e5a4/index.html"
}
@article{Otto,
author = "Mark Otto",
title = "Bootstrap from Twitter",
year = "2011",
note = "https://blog.twitter.com/developer/en\_us/a/2011/bootstrap-twitter.html"
}
@article{Bootstrap,
author = "Bootstrap Authors, Twitter Inc",
title = "Bootstrap Repository",
year = "2011",
note = "https://github.com/twbs/bootstrap"
}
@article{Otto,
author = "Mark Otto",
title = "Say hello to Bootstrap 2.0",
year = "2012",
note = "https://web.archive.org/web/20120203191214/https://dev.twitter.com/blog/say-hello-to-bootstrap-2"
}
@article{Django,
author = "Django Software Foundation",
title = "django.contrib.auth, User Model",
year = "2018",
note = "https://docs.djangoproject.com/en/2.1/ref/contrib/auth/"
}
@article{Django,
author = "Djangogirlsgirls",
title = "Creating a blog post model",
year = "2018",
note = "https://tutorial.djangogirls.org/en/django_models/"
}
@article{Microsoft,
author = "Microsoft",
title = "Extensions for the Visual Studio family of products",
year = "2018",
note = "https://marketplace.visualstudio.com/"
}
@article{Computer,
author = "The Computer Language Company",
title = "Definition of: user permissions",
year = "2018",
note = "https://www.pcmag.com/encyclopedia/term/58231/user-permissions"
}
@@ -16,7 +16,7 @@
\line(1,0){1}\\
\vspace{2.0cm}
\textbf{\huge{Esther Kleinhenz}}\\
\textbf{\huge{Esther Beate Kleinhenz}}\\
\vspace{0.4cm}
{\Large \textbf{Matrikelnummer: }2649270}\\
\vspace{2.5cm}
@@ -55,10 +55,7 @@ Nürnberg, {\today}\\
%\indent verfasst.
%\vspace{1.5cm}
\indent Hiermit erkläre ich, dass die vorliegende Arbeit von mir selbständig verfasst und nicht\\
\indent anderweitig für Prüfungszwecke vorgelegt wurde, keine anderen als die angegebenen\\
\indent Quellen oder Hilfsmittel benutzt sowie wörtliche oder sinngemäße Zitate als solche\\
\indent gekennzeichnet wurden.
Hiermit erkläre ich, dass die vorliegende Arbeit von mir selbständig verfasst und nicht anderweitig für Prüfungszwecke vorgelegt wurde, keine anderen als die angegebenen. Quellen oder Hilfsmittel benutzt sowie wörtliche oder sinngemäße Zitate als solche gekennzeichnet wurden.
\vspace{0.5cm}
@@ -68,15 +65,15 @@ Nürnberg, {\today}\\
\vspace{1.0cm}
\indent Katja Cornelia Hader \\
\indent E-mail: {haderka56442@th-nuernberg.de}
\indent Esther Beate Kleinhenz \\
\indent E-mail: {kleinhenzes60188@th-nuernberg.de}
\vspace{\fill}
\indent Studiengang Wirtschaftsinformatik\\
\indent Studiengang Media Engineering\\
\indent Georg Simon Ohm\\
\indent Technische Hochschule Nürnberg\\
\indent Keßlerplatz 12\\
\indent Ke"slerplatz 12\\
\indent 90489 Nürnberg\\
\indent Deutschland
+11
View File
@@ -0,0 +1,11 @@
version: '3'
services:
web:
build: .
command: bash -c "python manage.py makemigrations && python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
container_name: esther_kleinhenz_ba
volumes:
- .:/esther_kleinhenz_ba
ports:
- "8000:8000"
+573 -402
View File
@@ -1,402 +1,573 @@
[16/Oct/2018 19:58:49] INFO [mysite:150] test
[16/Oct/2018 20:00:13] INFO [mysite:150] test
[16/Oct/2018 20:00:44] INFO [mysite:150] test
[18/Oct/2018 16:49:10] INFO [mysite:150] test
[18/Oct/2018 16:50:20] INFO [mysite:149] test
[18/Oct/2018 16:53:59] INFO [mysite:149] test
[18/Oct/2018 17:02:15] INFO [mysite:149] Here ist the log
[18/Oct/2018 17:03:03] INFO [mysite:149] Here ist the log
[18/Oct/2018 17:04:21] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:08:44] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:22:00] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:29:57] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:33:51] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:37:15] INFO [mysite:147] hei
[18/Oct/2018 17:38:10] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:40:53] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:41:34] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:41:45] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:42:04] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:47:59] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:49:27] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:49:43] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:50:18] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:04:38] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:07:12] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:08:17] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:09:29] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:10:27] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:10:59] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:13:11] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:14:22] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:14:45] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:17:07] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:23:37] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:42:53] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:43:28] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:46:58] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 19:12:55] INFO [mysite:147] CustomUser object (25)
[19/Oct/2018 09:18:42] INFO [mysite:147] CustomUser object (25)
[19/Oct/2018 09:19:10] INFO [mysite:148] <QuerySet []>
[19/Oct/2018 10:24:35] INFO [mysite:148] <QuerySet []>
[19/Oct/2018 16:52:08] INFO [mysite:148] <QuerySet []>
[19/Oct/2018 17:33:50] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:38:33] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:39:08] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:41:43] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:42:00] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:44:10] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:47:28] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:48:28] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:56:41] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:06:57] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:07:17] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:07:31] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:12:56] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:15:29] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 17:20:58] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 17:54:57] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 18:03:09] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 18:18:52] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 19:14:57] INFO [mysite:183] None
[20/Oct/2018 19:15:00] INFO [mysite:183] test
[20/Oct/2018 19:15:45] INFO [mysite:183] test
[20/Oct/2018 19:17:52] INFO [mysite:183] test
[20/Oct/2018 19:18:31] INFO [mysite:183] test
[20/Oct/2018 19:19:01] INFO [mysite:183] test
[20/Oct/2018 19:20:08] INFO [mysite:184] test
[20/Oct/2018 19:24:45] INFO [mysite:184] None
[20/Oct/2018 19:26:30] INFO [mysite:184] None
[20/Oct/2018 19:29:44] INFO [mysite:184] test
[20/Oct/2018 19:32:09] INFO [mysite:184] test
[20/Oct/2018 19:33:14] INFO [mysite:184] 5
[20/Oct/2018 22:26:02] INFO [mysite:184] hi
[20/Oct/2018 22:26:09] INFO [mysite:184] 2
[20/Oct/2018 22:26:39] INFO [mysite:184] 2
[20/Oct/2018 22:35:49] INFO [mysite:183] 2
[20/Oct/2018 22:35:55] INFO [mysite:183] 3
[20/Oct/2018 22:51:06] INFO [mysite:183] hi
[20/Oct/2018 22:53:24] INFO [mysite:183] hi
[20/Oct/2018 22:54:12] INFO [mysite:183] hi
[20/Oct/2018 22:57:38] INFO [mysite:181] <QuerySet [<Post: first post>, <Post: second bla>, <Post: Third one>]>
[20/Oct/2018 22:57:39] INFO [mysite:184] hi
[20/Oct/2018 22:59:11] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 22:59:11] INFO [mysite:184] hi
[20/Oct/2018 23:01:53] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:01:53] INFO [mysite:184] hi
[20/Oct/2018 23:02:06] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:02:06] INFO [mysite:184] 5
[20/Oct/2018 23:02:22] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:02:22] INFO [mysite:184] 5
[20/Oct/2018 23:02:45] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:02:45] INFO [mysite:184] 5
[20/Oct/2018 23:03:07] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:03:07] INFO [mysite:184] 5
[20/Oct/2018 23:03:46] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:03:46] INFO [mysite:184] 5
[20/Oct/2018 23:04:35] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:04:35] INFO [mysite:184] 5
[20/Oct/2018 23:06:49] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:06:49] INFO [mysite:184] hi
[20/Oct/2018 23:15:41] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:15:41] INFO [mysite:184] hi
[20/Oct/2018 23:17:19] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:17:19] INFO [mysite:184] hi
[20/Oct/2018 23:17:38] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:17:38] INFO [mysite:184] hi
[20/Oct/2018 23:19:11] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:19:11] INFO [mysite:184] hi
[20/Oct/2018 23:24:22] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:23] INFO [mysite:184] hi
[20/Oct/2018 23:24:42] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:42] INFO [mysite:184] hi
[20/Oct/2018 23:24:49] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:49] INFO [mysite:184] hi
[20/Oct/2018 23:24:54] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:54] INFO [mysite:184] test
[20/Oct/2018 23:25:01] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:25:01] INFO [mysite:184] third
[20/Oct/2018 23:28:54] INFO [mysite:182] None
[20/Oct/2018 23:29:00] INFO [mysite:182] hi
[20/Oct/2018 23:29:42] INFO [mysite:182] hi
[20/Oct/2018 23:30:53] INFO [mysite:182] hi
[20/Oct/2018 23:34:29] INFO [mysite:184] hi
[20/Oct/2018 23:34:34] INFO [mysite:54] <QuerySet [<Post: second bla>]>
[20/Oct/2018 23:38:12] INFO [mysite:54] <QuerySet [<Post: second bla>]>
[20/Oct/2018 23:38:18] INFO [mysite:184] None
[20/Oct/2018 23:38:25] INFO [mysite:184] hi
[20/Oct/2018 23:40:03] INFO [mysite:184] hi
[20/Oct/2018 23:40:10] INFO [mysite:184] first
[20/Oct/2018 23:41:01] INFO [mysite:184] first
[20/Oct/2018 23:41:12] INFO [mysite:184] h
[20/Oct/2018 23:41:28] INFO [mysite:184] h
[20/Oct/2018 23:41:32] INFO [mysite:184] h
[20/Oct/2018 23:41:38] INFO [mysite:184] h
[20/Oct/2018 23:41:40] INFO [mysite:184] hi
[20/Oct/2018 23:41:50] INFO [mysite:184] hi
[20/Oct/2018 23:41:56] INFO [mysite:184] taggit
[21/Oct/2018 10:35:50] INFO [mysite:184] taggit
[21/Oct/2018 10:35:55] INFO [mysite:184]
[21/Oct/2018 10:38:17] INFO [mysite:184] None
[21/Oct/2018 12:59:59] INFO [mysite:184] None
[22/Oct/2018 14:31:54] INFO [mysite:184] None
[22/Oct/2018 14:32:03] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:40:08] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:43:18] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:43:31] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:50:32] INFO [mysite:150] esthi
[22/Oct/2018 14:50:32] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 14:51:10] INFO [mysite:150] stefan
[22/Oct/2018 14:51:10] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:56:36] INFO [mysite:150] stefan
[22/Oct/2018 14:56:36] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:56:56] INFO [mysite:150] stefan
[22/Oct/2018 14:56:56] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:57:15] INFO [mysite:150] stefan
[22/Oct/2018 14:57:15] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:57:39] INFO [mysite:150] stefan
[22/Oct/2018 14:57:39] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:58:53] INFO [mysite:150] stefan
[22/Oct/2018 14:58:53] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:59:01] INFO [mysite:150] stefan
[22/Oct/2018 14:59:01] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:59:55] INFO [mysite:150] stefan
[22/Oct/2018 14:59:55] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:02:16] INFO [mysite:150] stefan
[22/Oct/2018 15:02:16] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:03:09] INFO [mysite:150] stefan
[22/Oct/2018 15:03:09] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:03:41] INFO [mysite:150] stefan
[22/Oct/2018 15:03:41] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:05:17] INFO [mysite:184] None
[22/Oct/2018 15:05:21] INFO [mysite:184] hi
[22/Oct/2018 15:07:51] INFO [mysite:185] None
[22/Oct/2018 15:07:53] INFO [mysite:150] stefan
[22/Oct/2018 15:07:53] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:08:37] INFO [mysite:150] stefan
[22/Oct/2018 15:08:37] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:09:08] INFO [mysite:151] stefan
[22/Oct/2018 15:09:08] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:09:58] INFO [mysite:151] stefan
[22/Oct/2018 15:09:58] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:11:00] INFO [mysite:151] stefan
[22/Oct/2018 15:11:00] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:11:41] INFO [mysite:151] stefan
[22/Oct/2018 15:11:41] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:12:14] INFO [mysite:151] stefan
[22/Oct/2018 15:12:14] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:14:15] INFO [mysite:151] stefan
[22/Oct/2018 15:14:15] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 17:37:38] INFO [mysite:154] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 17:38:19] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:39:44] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:39:44] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:41:03] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:41:19] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:41:20] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:07] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:45:07] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:32] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:45:32] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:47] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:45:47] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:47] INFO [mysite:160] ['bamberg']
[22/Oct/2018 17:45:47] INFO [mysite:160] ['bamberg', 'test']
[22/Oct/2018 17:49:21] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:49:21] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:49:21] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:49:21] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:51:20] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:51:20] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:51:20] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:51:20] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:51:27] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:51:27] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:51:28] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:51:28] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:53:13] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:53:55] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:53:55] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:53:55] INFO [mysite:158] ['bamberg']
[22/Oct/2018 17:53:55] INFO [mysite:158] ['bamberg', 'test']
[22/Oct/2018 17:54:44] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:54:44] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:55:02] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:55:02] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:55:02] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:55:02] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:55:14] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:55:14] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:55:14] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:55:14] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:56:12] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:56:12] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:56:12] INFO [mysite:159] bamberg
[22/Oct/2018 17:56:12] INFO [mysite:159] test
[22/Oct/2018 17:56:34] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:56:34] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:56:34] INFO [mysite:159] bamberg
[22/Oct/2018 17:56:34] INFO [mysite:159] test
[22/Oct/2018 17:57:48] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:57:48] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:57:49] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:57:49] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:59:46] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:59:46] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:59:46] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:59:46] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:06] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:06] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:00:06] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:06] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:15] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:15] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:00:15] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:15] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:01:25] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:01:25] INFO [mysite:158] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:01:25] INFO [mysite:158] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:01:45] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:02:47] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:03:12] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:03:12] INFO [mysite:158] <QuerySet []>
[22/Oct/2018 18:03:12] INFO [mysite:158] <QuerySet []>
[22/Oct/2018 18:04:45] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:05:24] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:05:24] INFO [mysite:157] bamberg
[22/Oct/2018 18:05:24] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:05:24] INFO [mysite:157] test
[22/Oct/2018 18:05:24] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:13:35] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:13:35] INFO [mysite:157] bamberg
[22/Oct/2018 18:14:16] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:14:16] INFO [mysite:157] bamberg
[22/Oct/2018 18:19:16] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:19:16] INFO [mysite:157] bamberg
[22/Oct/2018 18:38:14] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:38:14] INFO [mysite:157] bamberg
[22/Oct/2018 18:41:50] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:41:50] INFO [mysite:157] bamberg
[22/Oct/2018 18:41:50] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:41:50] INFO [mysite:157] test
[22/Oct/2018 18:41:50] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:45:55] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:45:55] INFO [mysite:157] bamberg
[22/Oct/2018 18:46:39] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:46:39] INFO [mysite:157] bamberg
[22/Oct/2018 18:46:39] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:46:39] INFO [mysite:157] test
[22/Oct/2018 18:46:39] INFO [mysite:159] <QuerySet [<Post: first post>, <Post: second bla>, <Post: Third one>]>
[22/Oct/2018 18:47:12] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:47:12] INFO [mysite:157] stefan
[22/Oct/2018 18:47:12] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:48:22] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:48:22] INFO [mysite:157] stefan
[22/Oct/2018 18:48:22] INFO [mysite:159] <QuerySet [<Post: Crazy>]>
[22/Oct/2018 18:50:37] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:50:38] INFO [mysite:157] stefan
[22/Oct/2018 18:52:01] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:52:01] INFO [mysite:157] stefan
[22/Oct/2018 18:52:36] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:52:36] INFO [mysite:157] stefan
[22/Oct/2018 18:53:07] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:53:07] INFO [mysite:157] stefan
[22/Oct/2018 18:53:20] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:53:20] INFO [mysite:157] bamberg
[22/Oct/2018 18:53:20] INFO [mysite:157] test
[22/Oct/2018 18:54:05] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:54:05] INFO [mysite:157] bamberg
[22/Oct/2018 18:54:20] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:54:21] INFO [mysite:157] bamberg
[22/Oct/2018 18:54:21] INFO [mysite:157] test
[22/Oct/2018 18:54:46] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:54:46] INFO [mysite:156] bamberg
[22/Oct/2018 18:54:46] INFO [mysite:156] test
[22/Oct/2018 18:55:43] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:55:43] INFO [mysite:156] bamberg
[22/Oct/2018 18:55:43] INFO [mysite:156] test
[24/Oct/2018 15:10:56] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:10:56] INFO [mysite:156] stefan
[24/Oct/2018 15:11:37] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:11:37] INFO [mysite:156] stefan
[24/Oct/2018 15:11:37] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:13:48] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:13:48] INFO [mysite:156] stefan
[24/Oct/2018 15:13:48] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:15:46] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:15:46] INFO [mysite:157] stefan
[24/Oct/2018 15:15:55] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:15:55] INFO [mysite:157] stefan
[24/Oct/2018 15:15:55] INFO [mysite:159] <QuerySet []>
[24/Oct/2018 15:17:17] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:17:17] INFO [mysite:156] stefan
[24/Oct/2018 15:17:17] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:18:27] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:18:27] INFO [mysite:156] stefan
[24/Oct/2018 15:18:27] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:21:10] INFO [mysite:150] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[24/Oct/2018 15:21:10] INFO [mysite:154] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:21:10] INFO [mysite:158] stefan
[24/Oct/2018 15:21:10] INFO [mysite:160] <QuerySet []>
[24/Oct/2018 15:22:12] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:22:12] INFO [mysite:156] ['stefan']
[24/Oct/2018 15:22:12] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:22:29] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:22:29] INFO [mysite:156] ['stefan']
[24/Oct/2018 15:22:57] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:22:57] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:23:36] INFO [mysite:152] <QuerySet [<Tag: stefan>, <Tag: test>]>
[24/Oct/2018 15:23:36] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:23:36] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:24:29] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>, <Tag: wieder>]>
[24/Oct/2018 15:24:29] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:24:29] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:24:29] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:26:03] INFO [mysite:152] <QuerySet [<Tag: lukas>, <Tag: hi>]>
[24/Oct/2018 15:26:03] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:26:03] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: second bla>]>
[24/Oct/2018 15:51:54] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>, <Tag: wieder>]>
[24/Oct/2018 15:51:55] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:51:55] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:51:55] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:53:01] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[24/Oct/2018 15:53:01] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:53:01] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:53:28] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>, <Tag: this is a new one>]>
[24/Oct/2018 15:53:28] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:53:28] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:53:28] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 18:41:24] INFO [mysite:186] None
[24/Oct/2018 18:41:31] INFO [mysite:186] bla
[24/Oct/2018 18:42:12] INFO [mysite:186] avoiddouble
[24/Oct/2018 18:42:20] INFO [mysite:186] hi
[24/Oct/2018 18:56:30] INFO [mysite:158] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[24/Oct/2018 18:56:30] INFO [mysite:161] <QuerySet []>
[24/Oct/2018 18:56:30] INFO [mysite:161] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 18:56:45] INFO [mysite:190] None
[24/Oct/2018 18:56:49] INFO [mysite:190] bamberg
[24/Oct/2018 18:57:29] INFO [mysite:189] bamberg
[24/Oct/2018 19:00:30] INFO [mysite:189] None
[24/Oct/2018 19:00:30] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:00:33] INFO [mysite:189] hi
[24/Oct/2018 19:00:33] INFO [mysite:191] <QuerySet [<Post: second bla>, <Post: Third one>]>
[24/Oct/2018 19:00:38] INFO [mysite:189] bamberg
[24/Oct/2018 19:00:38] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:00:54] INFO [mysite:189] bamberg
[24/Oct/2018 19:00:54] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:01:16] INFO [mysite:189] test
[24/Oct/2018 19:01:16] INFO [mysite:191] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[24/Oct/2018 19:01:20] INFO [mysite:189] third
[24/Oct/2018 19:01:20] INFO [mysite:191] <QuerySet [<Post: Third one>]>
[24/Oct/2018 19:01:35] INFO [mysite:189] None
[24/Oct/2018 19:01:35] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:01:40] INFO [mysite:189] bamberg
[24/Oct/2018 19:01:40] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:03:03] INFO [mysite:189] None
[24/Oct/2018 19:03:03] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:03:08] INFO [mysite:189] bamberg
[24/Oct/2018 19:03:08] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:03:28] INFO [mysite:189] bamberg
[24/Oct/2018 19:03:28] INFO [mysite:191] <QuerySet [<Post: Third one>]>
[24/Oct/2018 19:03:45] INFO [mysite:189] bamberg
[24/Oct/2018 19:03:45] INFO [mysite:191] <QuerySet [<Post: Third one>, <Post: here i go again>]>
[30/Oct/2018 13:40:36] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:40:36] INFO [mysite:138] hi
[30/Oct/2018 13:40:36] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:40:36] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:40:55] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:40:55] INFO [mysite:138] hi
[30/Oct/2018 13:40:55] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:40:55] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:41:03] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:41:03] INFO [mysite:138] hi
[30/Oct/2018 13:41:03] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:41:03] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:41:06] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:41:06] INFO [mysite:138] hi
[30/Oct/2018 13:41:06] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:41:06] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:36:42] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 15:36:45] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 15:36:45] INFO [mysite:138] hi
[30/Oct/2018 15:36:46] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:36:46] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:37:06] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 15:39:26] INFO [mysite:199] None
[30/Oct/2018 15:39:32] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:39:47] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:39:47] INFO [mysite:138] hi
[30/Oct/2018 15:39:47] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:39:47] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:40:32] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:40:36] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:40:36] INFO [mysite:138] hi
[30/Oct/2018 15:40:36] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:40:36] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:40:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:41:00] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:41:00] INFO [mysite:138] hi
[30/Oct/2018 15:41:00] INFO [mysite:147] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:41:00] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:41:27] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:41:28] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:41:30] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:41:30] INFO [mysite:138] hi
[30/Oct/2018 15:41:30] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:41:30] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:42:31] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:42:31] INFO [mysite:138] hi
[30/Oct/2018 15:42:31] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:42:31] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:42:34] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:42:34] INFO [mysite:138] hi
[30/Oct/2018 15:42:34] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:42:34] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:45:00] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:45:03] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:45:03] INFO [mysite:138] hi
[30/Oct/2018 15:45:34] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:45:37] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:45:37] INFO [mysite:138] hi
[30/Oct/2018 15:45:37] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 15:45:37] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:45:56] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:46:00] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:46:00] INFO [mysite:138] hi
[30/Oct/2018 15:46:00] INFO [mysite:146] <bound method BaseModelForm.clean of <NewTagForm bound=True, valid=True, fields=(m_tags)>>
[30/Oct/2018 15:46:00] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:46:28] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:46:31] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:46:31] INFO [mysite:138] hi
[30/Oct/2018 15:46:31] INFO [mysite:146] <QueryDict: {'csrfmiddlewaretoken': ['lGD79wzSnukIJUbQ0zC3CwkitKoFy7XPhEq6uLMlu7TqEOjMAAjsCoC5BG8qAwQa'], 'm_tags': ['hi']}>
[30/Oct/2018 15:46:31] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:47:27] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:47:30] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:47:30] INFO [mysite:138] hi
[30/Oct/2018 15:47:30] INFO [mysite:146] ['hi']
[30/Oct/2018 15:47:30] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:49:43] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:49:43] INFO [mysite:138] hi
[30/Oct/2018 15:50:08] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:50:12] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:50:12] INFO [mysite:138] hi
[30/Oct/2018 15:50:59] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:51:02] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:51:02] INFO [mysite:138] hi
[30/Oct/2018 15:51:40] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:51:43] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:51:43] INFO [mysite:144] ['hi']
[30/Oct/2018 15:52:31] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:52:31] INFO [mysite:144] 'hi'
[30/Oct/2018 15:53:10] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:53:10] INFO [mysite:144] 'hi'
[30/Oct/2018 15:53:50] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:53:50] INFO [mysite:144] 'hi'
[30/Oct/2018 15:54:30] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:54:32] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:54:33] INFO [mysite:144] 'hi'
[30/Oct/2018 15:54:33] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:54:55] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:54:55] INFO [mysite:144] 'hi'
[30/Oct/2018 15:54:55] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:54:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:54:58] INFO [mysite:144] 'hi'
[30/Oct/2018 15:54:58] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 15:56:02] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:56:04] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:56:51] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:56:54] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:56:56] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:57:04] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:59:17] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 15:59:30] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:01:18] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:02:15] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:02:54] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:05:29] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:05:29] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 16:05:29] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:05:33] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:05:33] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 16:05:33] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:05:59] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:06:04] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:06:04] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 16:06:04] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:10:45] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:10:48] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:10:48] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: neu>]>
[30/Oct/2018 16:10:48] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:16:10] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:16:13] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:16:13] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: neu>]>
[30/Oct/2018 16:16:53] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:16:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:16:57] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: neu>]>
[30/Oct/2018 16:16:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:17:19] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:17:19] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: neu>]>
[30/Oct/2018 16:17:20] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:17:58] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:17:58] INFO [mysite:145] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: neu>]>
[30/Oct/2018 16:17:58] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:21:21] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:21:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:21:57] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist']
[30/Oct/2018 16:21:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:22:43] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:22:47] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:22:47] INFO [mysite:140] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist']
[30/Oct/2018 16:22:47] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:23:14] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:23:14] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:23:18] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:23:18] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:23:18] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:23:18] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:27:39] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:27:39] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:27:43] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:27:43] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:27:43] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:27:43] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:28:55] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:28:55] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:28:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:28:57] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:28:57] INFO [mysite:146] worked out
[30/Oct/2018 16:28:58] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:28:58] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:29:16] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:29:16] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:29:19] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:29:19] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:29:19] INFO [mysite:146] worked out
[30/Oct/2018 16:29:37] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:29:37] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:29:40] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:29:40] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:32:08] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:32:08] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:32:11] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:32:11] INFO [mysite:137] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:34:03] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:34:03] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:34:05] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:34:05] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:36:53] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:36:53] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:36:58] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:36:58] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:39:53] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:39:53] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:39:53] INFO [mysite:148] worked out
[30/Oct/2018 16:39:53] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:39:55] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:39:55] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:39:55] INFO [mysite:148] worked out
[30/Oct/2018 16:39:55] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:09] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:12] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:12] INFO [mysite:143] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:40:12] INFO [mysite:148] worked out
[30/Oct/2018 16:40:12] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:35] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:38] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:38] INFO [mysite:139] form is valid
[30/Oct/2018 16:40:38] INFO [mysite:144] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:40:38] INFO [mysite:149] worked out
[30/Oct/2018 16:40:38] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:51] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:55] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:40:55] INFO [mysite:139] form is valid
[30/Oct/2018 16:40:55] INFO [mysite:144] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:40:55] INFO [mysite:149] worked out
[30/Oct/2018 16:40:56] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:41:13] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:41:16] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:41:16] INFO [mysite:139] form is valid
[30/Oct/2018 16:41:16] INFO [mysite:144] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:41:16] INFO [mysite:149] worked out
[30/Oct/2018 16:41:16] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:41:37] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:41:40] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:41:40] INFO [mysite:137] post method
[30/Oct/2018 16:41:40] INFO [mysite:140] form is valid
[30/Oct/2018 16:41:41] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:41:41] INFO [mysite:150] worked out
[30/Oct/2018 16:41:41] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:00] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:02] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:06] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:06] INFO [mysite:137] post method
[30/Oct/2018 16:42:06] INFO [mysite:140] form is valid
[30/Oct/2018 16:42:06] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:42:06] INFO [mysite:150] worked out
[30/Oct/2018 16:42:06] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:23] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:26] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:26] INFO [mysite:137] post method
[30/Oct/2018 16:42:26] INFO [mysite:140] form is valid
[30/Oct/2018 16:42:27] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:42:27] INFO [mysite:150] worked out
[30/Oct/2018 16:42:27] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:44] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:47] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:42:47] INFO [mysite:137] post method
[30/Oct/2018 16:42:47] INFO [mysite:140] form is valid
[30/Oct/2018 16:42:47] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:42:47] INFO [mysite:150] worked out
[30/Oct/2018 16:42:48] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:43:28] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:43:32] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:43:32] INFO [mysite:137] post method
[30/Oct/2018 16:43:32] INFO [mysite:140] form is valid
[30/Oct/2018 16:43:32] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:43:32] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:43:41] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:43:41] INFO [mysite:137] post method
[30/Oct/2018 16:43:41] INFO [mysite:140] form is valid
[30/Oct/2018 16:43:41] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:43:41] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:44:18] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:44:26] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:44:26] INFO [mysite:137] post method
[30/Oct/2018 16:44:26] INFO [mysite:140] form is valid
[30/Oct/2018 16:44:26] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:44:26] INFO [mysite:147] m_tags
[30/Oct/2018 16:44:45] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:44:45] INFO [mysite:137] post method
[30/Oct/2018 16:44:45] INFO [mysite:140] form is valid
[30/Oct/2018 16:44:45] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:44:45] INFO [mysite:147] ['testag']
[30/Oct/2018 16:44:47] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:44:47] INFO [mysite:137] post method
[30/Oct/2018 16:44:47] INFO [mysite:140] form is valid
[30/Oct/2018 16:44:47] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:44:47] INFO [mysite:147] ['testag']
[30/Oct/2018 16:45:17] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:45:17] INFO [mysite:137] post method
[30/Oct/2018 16:45:17] INFO [mysite:140] form is valid
[30/Oct/2018 16:45:17] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:45:17] INFO [mysite:147] ['testag']
[30/Oct/2018 16:45:22] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:45:22] INFO [mysite:137] post method
[30/Oct/2018 16:45:22] INFO [mysite:140] form is valid
[30/Oct/2018 16:45:22] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:45:22] INFO [mysite:147] ['test']
[30/Oct/2018 16:45:48] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:45:48] INFO [mysite:137] post method
[30/Oct/2018 16:45:48] INFO [mysite:140] form is valid
[30/Oct/2018 16:45:48] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:45:48] INFO [mysite:147] ['test']
[30/Oct/2018 16:45:53] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:45:53] INFO [mysite:137] post method
[30/Oct/2018 16:45:53] INFO [mysite:140] form is valid
[30/Oct/2018 16:45:53] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:45:53] INFO [mysite:147] ['hi']
[30/Oct/2018 16:47:04] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:47:04] INFO [mysite:137] post method
[30/Oct/2018 16:47:04] INFO [mysite:140] form is valid
[30/Oct/2018 16:47:04] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:47:04] INFO [mysite:147] ['hi']
[30/Oct/2018 16:47:07] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:47:07] INFO [mysite:137] post method
[30/Oct/2018 16:47:07] INFO [mysite:140] form is valid
[30/Oct/2018 16:47:07] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:47:07] INFO [mysite:147] ['hi']
[30/Oct/2018 16:48:06] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:48:06] INFO [mysite:137] post method
[30/Oct/2018 16:48:06] INFO [mysite:140] form is valid
[30/Oct/2018 16:48:06] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:48:06] INFO [mysite:147] ['hi']
[30/Oct/2018 16:48:09] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:48:09] INFO [mysite:137] post method
[30/Oct/2018 16:48:09] INFO [mysite:140] form is valid
[30/Oct/2018 16:48:09] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:48:09] INFO [mysite:147] ['hi']
[30/Oct/2018 16:48:46] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:49:21] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:49:24] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:49:24] INFO [mysite:137] post method
[30/Oct/2018 16:49:24] INFO [mysite:140] form is valid
[30/Oct/2018 16:49:24] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:49:24] INFO [mysite:148] ['test']
[30/Oct/2018 16:49:45] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:49:45] INFO [mysite:137] post method
[30/Oct/2018 16:49:45] INFO [mysite:140] form is valid
[30/Oct/2018 16:49:45] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:49:45] INFO [mysite:148] ['hi']
[30/Oct/2018 16:49:51] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:49:51] INFO [mysite:137] post method
[30/Oct/2018 16:49:51] INFO [mysite:140] form is valid
[30/Oct/2018 16:49:51] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:49:51] INFO [mysite:148] ['hi']
[30/Oct/2018 16:50:57] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:50:57] INFO [mysite:137] post method
[30/Oct/2018 16:50:57] INFO [mysite:140] form is valid
[30/Oct/2018 16:50:57] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:50:57] INFO [mysite:148] hi
[30/Oct/2018 16:51:01] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:51:01] INFO [mysite:137] post method
[30/Oct/2018 16:51:01] INFO [mysite:140] form is valid
[30/Oct/2018 16:51:01] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:51:01] INFO [mysite:148] hi
[30/Oct/2018 16:51:03] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:51:03] INFO [mysite:137] post method
[30/Oct/2018 16:51:03] INFO [mysite:140] form is valid
[30/Oct/2018 16:51:03] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:51:03] INFO [mysite:148] hi
[30/Oct/2018 16:51:20] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:51:20] INFO [mysite:137] post method
[30/Oct/2018 16:51:20] INFO [mysite:140] form is valid
[30/Oct/2018 16:51:20] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt']
[30/Oct/2018 16:51:20] INFO [mysite:148] hi
[30/Oct/2018 16:51:20] INFO [mysite:152] worked out
[30/Oct/2018 16:51:20] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:51:35] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:51:35] INFO [mysite:137] post method
[30/Oct/2018 16:51:35] INFO [mysite:140] form is valid
[30/Oct/2018 16:51:35] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt', 'h', 'i']
[30/Oct/2018 16:51:35] INFO [mysite:148] hi
[30/Oct/2018 16:51:35] INFO [mysite:152] worked out
[30/Oct/2018 16:51:35] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:52:18] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:52:25] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:52:25] INFO [mysite:137] post method
[30/Oct/2018 16:52:25] INFO [mysite:140] form is valid
[30/Oct/2018 16:52:25] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'neu', 'mist', 'fritt', 'h', 'i']
[30/Oct/2018 16:52:25] INFO [mysite:148] hi
[30/Oct/2018 16:52:25] INFO [mysite:152] worked out
[30/Oct/2018 16:53:40] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:53:40] INFO [mysite:137] post method
[30/Oct/2018 16:53:40] INFO [mysite:140] form is valid
[30/Oct/2018 16:53:41] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:53:41] INFO [mysite:148] hi
[30/Oct/2018 16:53:41] INFO [mysite:152] worked out
[30/Oct/2018 16:53:45] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:53:45] INFO [mysite:137] post method
[30/Oct/2018 16:53:45] INFO [mysite:140] form is valid
[30/Oct/2018 16:53:45] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'h', 'i']
[30/Oct/2018 16:53:45] INFO [mysite:148] hi
[30/Oct/2018 16:53:45] INFO [mysite:152] worked out
[30/Oct/2018 16:55:22] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:55:22] INFO [mysite:137] post method
[30/Oct/2018 16:55:22] INFO [mysite:140] form is valid
[30/Oct/2018 16:55:22] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'h', 'i']
[30/Oct/2018 16:55:22] INFO [mysite:148] hi
[30/Oct/2018 16:55:22] INFO [mysite:152] worked out
[30/Oct/2018 16:55:25] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:55:25] INFO [mysite:137] post method
[30/Oct/2018 16:55:25] INFO [mysite:140] form is valid
[30/Oct/2018 16:55:25] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'h', 'i']
[30/Oct/2018 16:55:25] INFO [mysite:148] hi
[30/Oct/2018 16:55:25] INFO [mysite:152] worked out
[30/Oct/2018 16:55:46] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:55:46] INFO [mysite:137] post method
[30/Oct/2018 16:55:46] INFO [mysite:140] form is valid
[30/Oct/2018 16:55:46] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:55:46] INFO [mysite:148] hi
[30/Oct/2018 16:55:46] INFO [mysite:152] worked out
[30/Oct/2018 16:55:48] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:55:48] INFO [mysite:137] post method
[30/Oct/2018 16:55:48] INFO [mysite:140] form is valid
[30/Oct/2018 16:55:48] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'h', 'i']
[30/Oct/2018 16:55:48] INFO [mysite:148] hi
[30/Oct/2018 16:55:48] INFO [mysite:152] worked out
[30/Oct/2018 16:57:24] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:57:24] INFO [mysite:137] post method
[30/Oct/2018 16:57:24] INFO [mysite:140] form is valid
[30/Oct/2018 16:57:24] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second', 'h', 'i']
[30/Oct/2018 16:57:24] INFO [mysite:148] hi
[30/Oct/2018 16:57:24] INFO [mysite:150] worked out
[30/Oct/2018 16:57:41] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:57:41] INFO [mysite:137] post method
[30/Oct/2018 16:57:41] INFO [mysite:140] form is valid
[30/Oct/2018 16:57:41] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:57:41] INFO [mysite:148] hi
[30/Oct/2018 16:57:41] INFO [mysite:150] worked out
[30/Oct/2018 16:57:43] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:57:43] INFO [mysite:137] post method
[30/Oct/2018 16:57:43] INFO [mysite:140] form is valid
[30/Oct/2018 16:57:43] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:57:43] INFO [mysite:148] hi
[30/Oct/2018 16:57:43] INFO [mysite:150] worked out
[30/Oct/2018 16:58:02] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:58:02] INFO [mysite:137] post method
[30/Oct/2018 16:58:02] INFO [mysite:140] form is valid
[30/Oct/2018 16:58:02] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:58:02] INFO [mysite:148] hi
[30/Oct/2018 16:58:02] INFO [mysite:150] worked out
[30/Oct/2018 16:58:07] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:58:07] INFO [mysite:137] post method
[30/Oct/2018 16:58:07] INFO [mysite:140] form is valid
[30/Oct/2018 16:58:07] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:58:07] INFO [mysite:148] bayern
[30/Oct/2018 16:58:07] INFO [mysite:150] worked out
[30/Oct/2018 16:59:49] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:59:49] INFO [mysite:137] post method
[30/Oct/2018 16:59:49] INFO [mysite:140] form is valid
[30/Oct/2018 16:59:49] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:59:49] INFO [mysite:148] bayern
[30/Oct/2018 16:59:49] INFO [mysite:150] worked out
[30/Oct/2018 16:59:49] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:59:54] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 16:59:54] INFO [mysite:137] post method
[30/Oct/2018 16:59:54] INFO [mysite:140] form is valid
[30/Oct/2018 16:59:54] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 16:59:54] INFO [mysite:148] bayern
[30/Oct/2018 16:59:54] INFO [mysite:150] worked out
[30/Oct/2018 16:59:54] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:00:33] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:00:36] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:00:36] INFO [mysite:137] post method
[30/Oct/2018 17:00:36] INFO [mysite:140] form is valid
[30/Oct/2018 17:00:36] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 17:00:36] INFO [mysite:148] hi
[30/Oct/2018 17:00:36] INFO [mysite:150] worked out
[30/Oct/2018 17:00:37] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:00:58] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:01:01] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:01:01] INFO [mysite:137] post method
[30/Oct/2018 17:01:01] INFO [mysite:140] form is valid
[30/Oct/2018 17:01:01] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 17:01:01] INFO [mysite:148] test
[30/Oct/2018 17:01:01] INFO [mysite:150] worked out
[30/Oct/2018 17:01:01] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:01:06] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:01:06] INFO [mysite:137] post method
[30/Oct/2018 17:01:06] INFO [mysite:140] form is valid
[30/Oct/2018 17:01:06] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 17:01:06] INFO [mysite:148] testag
[30/Oct/2018 17:01:30] INFO [mysite:135] CustomUser object (1)
[30/Oct/2018 17:01:30] INFO [mysite:137] post method
[30/Oct/2018 17:01:30] INFO [mysite:140] form is valid
[30/Oct/2018 17:01:30] INFO [mysite:145] ['hi', 'first', 'test', 'bayern', 'second']
[30/Oct/2018 17:01:30] INFO [mysite:148] testag
[31/Oct/2018 14:51:36] INFO [mysite:141] CustomUser object (2)
[31/Oct/2018 14:51:39] INFO [mysite:141] CustomUser object (2)
[31/Oct/2018 14:52:57] INFO [mysite:142] CustomUser object (2)
[31/Oct/2018 14:53:00] INFO [mysite:142] CustomUser object (2)
[31/Oct/2018 14:54:10] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 14:54:12] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 14:55:09] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 14:55:38] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 14:55:41] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 14:56:20] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 14:58:29] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 14:58:31] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 14:58:43] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 14:59:00] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:18:53] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:19:02] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:19:04] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:20:10] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:21:24] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:21:27] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:24:55] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:27:16] INFO [mysite:147] CustomUser object (2)
[31/Oct/2018 17:27:59] INFO [mysite:147] CustomUser object (2)
[31/Oct/2018 17:28:02] INFO [mysite:147] CustomUser object (2)
[31/Oct/2018 17:29:13] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:29:17] INFO [mysite:146] CustomUser object (2)
[31/Oct/2018 17:32:43] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 17:32:45] INFO [mysite:144] CustomUser object (2)
[31/Oct/2018 17:42:25] INFO [mysite:186] None
[31/Oct/2018 17:43:06] INFO [mysite:186] None
[31/Oct/2018 17:43:07] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:43:13] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:44:14] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:44:17] INFO [mysite:175] <QuerySet [<Tag: hi>]>
[31/Oct/2018 17:44:17] INFO [mysite:178] <QuerySet [<Post: Hi there>]>
[31/Oct/2018 17:45:09] INFO [mysite:175] <QuerySet [<Tag: hi>]>
[31/Oct/2018 17:45:09] INFO [mysite:178] <QuerySet [<Post: Hi there>]>
[31/Oct/2018 17:45:15] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:45:24] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:45:24] INFO [mysite:146] post method
[31/Oct/2018 17:45:24] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 17:46:00] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:46:04] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:46:04] INFO [mysite:146] post method
[31/Oct/2018 17:46:05] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 17:46:05] INFO [mysite:156] hi
[31/Oct/2018 17:46:05] INFO [mysite:158] worked out
[31/Oct/2018 17:46:05] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:50:10] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:50:13] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:50:13] INFO [mysite:146] post method
[31/Oct/2018 17:51:14] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:51:14] INFO [mysite:146] post method
[31/Oct/2018 17:51:24] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:51:24] INFO [mysite:146] post method
[31/Oct/2018 17:51:39] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 17:56:24] INFO [mysite:144] CustomUser object (3)
[31/Oct/2018 18:04:35] INFO [mysite:146] post method
[31/Oct/2018 18:04:35] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:04:35] INFO [mysite:157] worked out
[31/Oct/2018 18:06:17] INFO [mysite:146] post method
[31/Oct/2018 18:06:18] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:06:18] INFO [mysite:157] worked out
[31/Oct/2018 18:07:46] INFO [mysite:146] post method
[31/Oct/2018 18:07:47] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:07:47] INFO [mysite:157] worked out
[31/Oct/2018 18:09:17] INFO [mysite:146] post method
[31/Oct/2018 18:09:18] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:09:40] INFO [mysite:146] post method
[31/Oct/2018 18:09:40] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:09:47] INFO [mysite:146] post method
[31/Oct/2018 18:09:48] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:10:12] INFO [mysite:146] post method
[31/Oct/2018 18:10:12] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:10:12] INFO [mysite:157] worked out
[31/Oct/2018 18:22:34] INFO [mysite:144] post method
[31/Oct/2018 18:22:51] INFO [mysite:145] post method
[31/Oct/2018 18:22:51] INFO [mysite:148] form is valid
[31/Oct/2018 18:22:51] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:23:10] INFO [mysite:145] post method
[31/Oct/2018 18:23:10] INFO [mysite:148] form is valid
[31/Oct/2018 18:23:10] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:23:10] INFO [mysite:156] hi
[31/Oct/2018 18:23:10] INFO [mysite:158] worked out
[31/Oct/2018 18:23:19] INFO [mysite:145] post method
[31/Oct/2018 18:23:19] INFO [mysite:148] form is valid
[31/Oct/2018 18:23:19] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:23:19] INFO [mysite:156] hi
[31/Oct/2018 18:23:19] INFO [mysite:158] worked out
[31/Oct/2018 18:23:23] INFO [mysite:145] post method
[31/Oct/2018 18:23:23] INFO [mysite:148] form is valid
[31/Oct/2018 18:23:23] INFO [mysite:153] ['hi', 'first', 'test', 'bayern', 'second']
[31/Oct/2018 18:23:23] INFO [mysite:156] tehere
[04/Nov/2018 14:50:11] INFO [mysite:186] None
[09/Nov/2018 08:16:30] INFO [mysite:59] <QuerySet [<Post: Hi there>, <Post: Bavaria>, <Post: test again>]>
[09/Nov/2018 08:16:32] INFO [mysite:59] <QuerySet [<Post: Hi there>]>
[09/Nov/2018 08:16:34] INFO [mysite:59] <QuerySet [<Post: Hi there>, <Post: test again>]>
[09/Nov/2018 08:16:53] INFO [mysite:187] None
[09/Nov/2018 15:25:20] INFO [mysite:187] None
+750
View File
@@ -0,0 +1,750 @@
[16/Oct/2018 19:58:49] INFO [mysite:150] test
[16/Oct/2018 20:00:13] INFO [mysite:150] test
[16/Oct/2018 20:00:44] INFO [mysite:150] test
[18/Oct/2018 16:49:10] INFO [mysite:150] test
[18/Oct/2018 16:50:20] INFO [mysite:149] test
[18/Oct/2018 16:53:59] INFO [mysite:149] test
[18/Oct/2018 17:02:15] INFO [mysite:149] Here ist the log
[18/Oct/2018 17:03:03] INFO [mysite:149] Here ist the log
[18/Oct/2018 17:04:21] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:08:44] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:22:00] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:29:57] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:33:51] INFO [mysite:150] Here ist the log
[18/Oct/2018 17:37:15] INFO [mysite:147] hei
[18/Oct/2018 17:38:10] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:40:53] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:41:34] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:41:45] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:42:04] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:47:59] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:49:27] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:49:43] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 17:50:18] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:04:38] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:07:12] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:08:17] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:09:29] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:10:27] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:10:59] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:13:11] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:14:22] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:14:45] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:17:07] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:23:37] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:42:53] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:43:28] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 18:46:58] INFO [mysite:147] CustomUser object (25)
[18/Oct/2018 19:12:55] INFO [mysite:147] CustomUser object (25)
[19/Oct/2018 09:18:42] INFO [mysite:147] CustomUser object (25)
[19/Oct/2018 09:19:10] INFO [mysite:148] <QuerySet []>
[19/Oct/2018 10:24:35] INFO [mysite:148] <QuerySet []>
[19/Oct/2018 16:52:08] INFO [mysite:148] <QuerySet []>
[19/Oct/2018 17:33:50] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:38:33] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:39:08] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:41:43] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:42:00] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:44:10] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:47:28] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:48:28] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 17:56:41] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:06:57] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:07:17] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:07:31] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:12:56] INFO [mysite:149] CustomUser object (25)
[19/Oct/2018 18:15:29] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 17:20:58] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 17:54:57] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 18:03:09] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 18:18:52] INFO [mysite:149] CustomUser object (25)
[20/Oct/2018 19:14:57] INFO [mysite:183] None
[20/Oct/2018 19:15:00] INFO [mysite:183] test
[20/Oct/2018 19:15:45] INFO [mysite:183] test
[20/Oct/2018 19:17:52] INFO [mysite:183] test
[20/Oct/2018 19:18:31] INFO [mysite:183] test
[20/Oct/2018 19:19:01] INFO [mysite:183] test
[20/Oct/2018 19:20:08] INFO [mysite:184] test
[20/Oct/2018 19:24:45] INFO [mysite:184] None
[20/Oct/2018 19:26:30] INFO [mysite:184] None
[20/Oct/2018 19:29:44] INFO [mysite:184] test
[20/Oct/2018 19:32:09] INFO [mysite:184] test
[20/Oct/2018 19:33:14] INFO [mysite:184] 5
[20/Oct/2018 22:26:02] INFO [mysite:184] hi
[20/Oct/2018 22:26:09] INFO [mysite:184] 2
[20/Oct/2018 22:26:39] INFO [mysite:184] 2
[20/Oct/2018 22:35:49] INFO [mysite:183] 2
[20/Oct/2018 22:35:55] INFO [mysite:183] 3
[20/Oct/2018 22:51:06] INFO [mysite:183] hi
[20/Oct/2018 22:53:24] INFO [mysite:183] hi
[20/Oct/2018 22:54:12] INFO [mysite:183] hi
[20/Oct/2018 22:57:38] INFO [mysite:181] <QuerySet [<Post: first post>, <Post: second bla>, <Post: Third one>]>
[20/Oct/2018 22:57:39] INFO [mysite:184] hi
[20/Oct/2018 22:59:11] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 22:59:11] INFO [mysite:184] hi
[20/Oct/2018 23:01:53] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:01:53] INFO [mysite:184] hi
[20/Oct/2018 23:02:06] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:02:06] INFO [mysite:184] 5
[20/Oct/2018 23:02:22] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:02:22] INFO [mysite:184] 5
[20/Oct/2018 23:02:45] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:02:45] INFO [mysite:184] 5
[20/Oct/2018 23:03:07] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:03:07] INFO [mysite:184] 5
[20/Oct/2018 23:03:46] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:03:46] INFO [mysite:184] 5
[20/Oct/2018 23:04:35] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:04:35] INFO [mysite:184] 5
[20/Oct/2018 23:06:49] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:06:49] INFO [mysite:184] hi
[20/Oct/2018 23:15:41] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:15:41] INFO [mysite:184] hi
[20/Oct/2018 23:17:19] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:17:19] INFO [mysite:184] hi
[20/Oct/2018 23:17:38] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:17:38] INFO [mysite:184] hi
[20/Oct/2018 23:19:11] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:19:11] INFO [mysite:184] hi
[20/Oct/2018 23:24:22] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:23] INFO [mysite:184] hi
[20/Oct/2018 23:24:42] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:42] INFO [mysite:184] hi
[20/Oct/2018 23:24:49] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:49] INFO [mysite:184] hi
[20/Oct/2018 23:24:54] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:24:54] INFO [mysite:184] test
[20/Oct/2018 23:25:01] INFO [mysite:181] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[20/Oct/2018 23:25:01] INFO [mysite:184] third
[20/Oct/2018 23:28:54] INFO [mysite:182] None
[20/Oct/2018 23:29:00] INFO [mysite:182] hi
[20/Oct/2018 23:29:42] INFO [mysite:182] hi
[20/Oct/2018 23:30:53] INFO [mysite:182] hi
[20/Oct/2018 23:34:29] INFO [mysite:184] hi
[20/Oct/2018 23:34:34] INFO [mysite:54] <QuerySet [<Post: second bla>]>
[20/Oct/2018 23:38:12] INFO [mysite:54] <QuerySet [<Post: second bla>]>
[20/Oct/2018 23:38:18] INFO [mysite:184] None
[20/Oct/2018 23:38:25] INFO [mysite:184] hi
[20/Oct/2018 23:40:03] INFO [mysite:184] hi
[20/Oct/2018 23:40:10] INFO [mysite:184] first
[20/Oct/2018 23:41:01] INFO [mysite:184] first
[20/Oct/2018 23:41:12] INFO [mysite:184] h
[20/Oct/2018 23:41:28] INFO [mysite:184] h
[20/Oct/2018 23:41:32] INFO [mysite:184] h
[20/Oct/2018 23:41:38] INFO [mysite:184] h
[20/Oct/2018 23:41:40] INFO [mysite:184] hi
[20/Oct/2018 23:41:50] INFO [mysite:184] hi
[20/Oct/2018 23:41:56] INFO [mysite:184] taggit
[21/Oct/2018 10:35:50] INFO [mysite:184] taggit
[21/Oct/2018 10:35:55] INFO [mysite:184]
[21/Oct/2018 10:38:17] INFO [mysite:184] None
[21/Oct/2018 12:59:59] INFO [mysite:184] None
[22/Oct/2018 14:31:54] INFO [mysite:184] None
[22/Oct/2018 14:32:03] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:40:08] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:43:18] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:43:31] INFO [mysite:150] CustomUser object (25)
[22/Oct/2018 14:50:32] INFO [mysite:150] esthi
[22/Oct/2018 14:50:32] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 14:51:10] INFO [mysite:150] stefan
[22/Oct/2018 14:51:10] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:56:36] INFO [mysite:150] stefan
[22/Oct/2018 14:56:36] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:56:56] INFO [mysite:150] stefan
[22/Oct/2018 14:56:56] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:57:15] INFO [mysite:150] stefan
[22/Oct/2018 14:57:15] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:57:39] INFO [mysite:150] stefan
[22/Oct/2018 14:57:39] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:58:53] INFO [mysite:150] stefan
[22/Oct/2018 14:58:53] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:59:01] INFO [mysite:150] stefan
[22/Oct/2018 14:59:01] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 14:59:55] INFO [mysite:150] stefan
[22/Oct/2018 14:59:55] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:02:16] INFO [mysite:150] stefan
[22/Oct/2018 15:02:16] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:03:09] INFO [mysite:150] stefan
[22/Oct/2018 15:03:09] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:03:41] INFO [mysite:150] stefan
[22/Oct/2018 15:03:41] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:05:17] INFO [mysite:184] None
[22/Oct/2018 15:05:21] INFO [mysite:184] hi
[22/Oct/2018 15:07:51] INFO [mysite:185] None
[22/Oct/2018 15:07:53] INFO [mysite:150] stefan
[22/Oct/2018 15:07:53] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:08:37] INFO [mysite:150] stefan
[22/Oct/2018 15:08:37] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:09:08] INFO [mysite:151] stefan
[22/Oct/2018 15:09:08] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:09:58] INFO [mysite:151] stefan
[22/Oct/2018 15:09:58] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:11:00] INFO [mysite:151] stefan
[22/Oct/2018 15:11:00] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:11:41] INFO [mysite:151] stefan
[22/Oct/2018 15:11:41] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:12:14] INFO [mysite:151] stefan
[22/Oct/2018 15:12:14] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 15:14:15] INFO [mysite:151] stefan
[22/Oct/2018 15:14:15] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 17:37:38] INFO [mysite:154] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 17:38:19] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:39:44] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:39:44] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:41:03] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:41:19] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:41:20] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:07] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:45:07] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:32] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:45:32] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:47] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:45:47] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:45:47] INFO [mysite:160] ['bamberg']
[22/Oct/2018 17:45:47] INFO [mysite:160] ['bamberg', 'test']
[22/Oct/2018 17:49:21] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:49:21] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:49:21] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:49:21] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:51:20] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:51:20] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:51:20] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:51:20] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:51:27] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:51:27] INFO [mysite:155] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:51:28] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:51:28] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:53:13] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:53:55] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:53:55] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:53:55] INFO [mysite:158] ['bamberg']
[22/Oct/2018 17:53:55] INFO [mysite:158] ['bamberg', 'test']
[22/Oct/2018 17:54:44] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:54:44] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:55:02] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:55:02] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:55:02] INFO [mysite:159] ['bamberg']
[22/Oct/2018 17:55:02] INFO [mysite:159] ['bamberg', 'test']
[22/Oct/2018 17:55:14] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:55:14] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:55:14] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:55:14] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:56:12] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:56:12] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:56:12] INFO [mysite:159] bamberg
[22/Oct/2018 17:56:12] INFO [mysite:159] test
[22/Oct/2018 17:56:34] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:56:34] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:56:34] INFO [mysite:159] bamberg
[22/Oct/2018 17:56:34] INFO [mysite:159] test
[22/Oct/2018 17:57:48] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:57:48] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:57:49] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:57:49] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 17:59:46] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:59:46] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 17:59:46] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 17:59:46] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:06] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:06] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:00:06] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:06] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:15] INFO [mysite:151] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:15] INFO [mysite:154] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:00:15] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:00:15] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:01:25] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:01:25] INFO [mysite:158] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:01:25] INFO [mysite:158] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:01:45] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:02:47] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:03:12] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:03:12] INFO [mysite:158] <QuerySet []>
[22/Oct/2018 18:03:12] INFO [mysite:158] <QuerySet []>
[22/Oct/2018 18:04:45] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:05:24] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:05:24] INFO [mysite:157] bamberg
[22/Oct/2018 18:05:24] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:05:24] INFO [mysite:157] test
[22/Oct/2018 18:05:24] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:13:35] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:13:35] INFO [mysite:157] bamberg
[22/Oct/2018 18:14:16] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:14:16] INFO [mysite:157] bamberg
[22/Oct/2018 18:19:16] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:19:16] INFO [mysite:157] bamberg
[22/Oct/2018 18:38:14] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:38:14] INFO [mysite:157] bamberg
[22/Oct/2018 18:41:50] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:41:50] INFO [mysite:157] bamberg
[22/Oct/2018 18:41:50] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:41:50] INFO [mysite:157] test
[22/Oct/2018 18:41:50] INFO [mysite:159] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[22/Oct/2018 18:45:55] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:45:55] INFO [mysite:157] bamberg
[22/Oct/2018 18:46:39] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:46:39] INFO [mysite:157] bamberg
[22/Oct/2018 18:46:39] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:46:39] INFO [mysite:157] test
[22/Oct/2018 18:46:39] INFO [mysite:159] <QuerySet [<Post: first post>, <Post: second bla>, <Post: Third one>]>
[22/Oct/2018 18:47:12] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:47:12] INFO [mysite:157] stefan
[22/Oct/2018 18:47:12] INFO [mysite:159] <QuerySet []>
[22/Oct/2018 18:48:22] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:48:22] INFO [mysite:157] stefan
[22/Oct/2018 18:48:22] INFO [mysite:159] <QuerySet [<Post: Crazy>]>
[22/Oct/2018 18:50:37] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:50:38] INFO [mysite:157] stefan
[22/Oct/2018 18:52:01] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:52:01] INFO [mysite:157] stefan
[22/Oct/2018 18:52:36] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:52:36] INFO [mysite:157] stefan
[22/Oct/2018 18:53:07] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[22/Oct/2018 18:53:07] INFO [mysite:157] stefan
[22/Oct/2018 18:53:20] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:53:20] INFO [mysite:157] bamberg
[22/Oct/2018 18:53:20] INFO [mysite:157] test
[22/Oct/2018 18:54:05] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:54:05] INFO [mysite:157] bamberg
[22/Oct/2018 18:54:20] INFO [mysite:153] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:54:21] INFO [mysite:157] bamberg
[22/Oct/2018 18:54:21] INFO [mysite:157] test
[22/Oct/2018 18:54:46] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:54:46] INFO [mysite:156] bamberg
[22/Oct/2018 18:54:46] INFO [mysite:156] test
[22/Oct/2018 18:55:43] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[22/Oct/2018 18:55:43] INFO [mysite:156] bamberg
[22/Oct/2018 18:55:43] INFO [mysite:156] test
[24/Oct/2018 15:10:56] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:10:56] INFO [mysite:156] stefan
[24/Oct/2018 15:11:37] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:11:37] INFO [mysite:156] stefan
[24/Oct/2018 15:11:37] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:13:48] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:13:48] INFO [mysite:156] stefan
[24/Oct/2018 15:13:48] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:15:46] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:15:46] INFO [mysite:157] stefan
[24/Oct/2018 15:15:55] INFO [mysite:153] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:15:55] INFO [mysite:157] stefan
[24/Oct/2018 15:15:55] INFO [mysite:159] <QuerySet []>
[24/Oct/2018 15:17:17] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:17:17] INFO [mysite:156] stefan
[24/Oct/2018 15:17:17] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:18:27] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:18:27] INFO [mysite:156] stefan
[24/Oct/2018 15:18:27] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:21:10] INFO [mysite:150] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[24/Oct/2018 15:21:10] INFO [mysite:154] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:21:10] INFO [mysite:158] stefan
[24/Oct/2018 15:21:10] INFO [mysite:160] <QuerySet []>
[24/Oct/2018 15:22:12] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:22:12] INFO [mysite:156] ['stefan']
[24/Oct/2018 15:22:12] INFO [mysite:158] <QuerySet []>
[24/Oct/2018 15:22:29] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:22:29] INFO [mysite:156] ['stefan']
[24/Oct/2018 15:22:57] INFO [mysite:152] <QuerySet [<Tag: stefan>]>
[24/Oct/2018 15:22:57] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:23:36] INFO [mysite:152] <QuerySet [<Tag: stefan>, <Tag: test>]>
[24/Oct/2018 15:23:36] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:23:36] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:24:29] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>, <Tag: wieder>]>
[24/Oct/2018 15:24:29] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:24:29] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:24:29] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:26:03] INFO [mysite:152] <QuerySet [<Tag: lukas>, <Tag: hi>]>
[24/Oct/2018 15:26:03] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:26:03] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: second bla>]>
[24/Oct/2018 15:51:54] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>, <Tag: wieder>]>
[24/Oct/2018 15:51:55] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:51:55] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:51:55] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:53:01] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[24/Oct/2018 15:53:01] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:53:01] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:53:28] INFO [mysite:152] <QuerySet [<Tag: bamberg>, <Tag: test>, <Tag: this is a new one>]>
[24/Oct/2018 15:53:28] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 15:53:28] INFO [mysite:155] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 15:53:28] INFO [mysite:155] <QuerySet []>
[24/Oct/2018 18:41:24] INFO [mysite:186] None
[24/Oct/2018 18:41:31] INFO [mysite:186] bla
[24/Oct/2018 18:42:12] INFO [mysite:186] avoiddouble
[24/Oct/2018 18:42:20] INFO [mysite:186] hi
[24/Oct/2018 18:56:30] INFO [mysite:158] <QuerySet [<Tag: bamberg>, <Tag: test>]>
[24/Oct/2018 18:56:30] INFO [mysite:161] <QuerySet []>
[24/Oct/2018 18:56:30] INFO [mysite:161] <QuerySet [<Post: Third one>, <Post: first post>, <Post: second bla>]>
[24/Oct/2018 18:56:45] INFO [mysite:190] None
[24/Oct/2018 18:56:49] INFO [mysite:190] bamberg
[24/Oct/2018 18:57:29] INFO [mysite:189] bamberg
[24/Oct/2018 19:00:30] INFO [mysite:189] None
[24/Oct/2018 19:00:30] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:00:33] INFO [mysite:189] hi
[24/Oct/2018 19:00:33] INFO [mysite:191] <QuerySet [<Post: second bla>, <Post: Third one>]>
[24/Oct/2018 19:00:38] INFO [mysite:189] bamberg
[24/Oct/2018 19:00:38] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:00:54] INFO [mysite:189] bamberg
[24/Oct/2018 19:00:54] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:01:16] INFO [mysite:189] test
[24/Oct/2018 19:01:16] INFO [mysite:191] <QuerySet [<Post: second bla>, <Post: first post>, <Post: Third one>]>
[24/Oct/2018 19:01:20] INFO [mysite:189] third
[24/Oct/2018 19:01:20] INFO [mysite:191] <QuerySet [<Post: Third one>]>
[24/Oct/2018 19:01:35] INFO [mysite:189] None
[24/Oct/2018 19:01:35] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:01:40] INFO [mysite:189] bamberg
[24/Oct/2018 19:01:40] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:03:03] INFO [mysite:189] None
[24/Oct/2018 19:03:03] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:03:08] INFO [mysite:189] bamberg
[24/Oct/2018 19:03:08] INFO [mysite:191] <QuerySet []>
[24/Oct/2018 19:03:28] INFO [mysite:189] bamberg
[24/Oct/2018 19:03:28] INFO [mysite:191] <QuerySet [<Post: Third one>]>
[24/Oct/2018 19:03:45] INFO [mysite:189] bamberg
[24/Oct/2018 19:03:45] INFO [mysite:191] <QuerySet [<Post: Third one>, <Post: here i go again>]>
[30/Oct/2018 12:25:09] INFO [mysite:56] <QuerySet [<Post: Hi there>]>
[30/Oct/2018 12:25:11] INFO [mysite:56] <QuerySet [<Post: Hi there>, <Post: Bavaria>]>
[30/Oct/2018 12:25:26] INFO [mysite:189] None
[30/Oct/2018 12:25:34] INFO [mysite:189] bayern
[30/Oct/2018 12:29:44] INFO [mysite:189] None
[30/Oct/2018 12:43:44] INFO [mysite:190] None
[30/Oct/2018 12:47:17] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:47:21] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:47:46] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:48:00] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:48:29] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:48:29] INFO [mysite:138] hi
[30/Oct/2018 12:49:23] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:49:26] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:49:26] INFO [mysite:138] hi
[30/Oct/2018 12:49:26] INFO [mysite:144] ['hi']
[30/Oct/2018 12:49:47] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:49:50] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:49:50] INFO [mysite:138] hi
[30/Oct/2018 12:49:50] INFO [mysite:144] ['test']
[30/Oct/2018 12:50:02] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:50:06] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:50:06] INFO [mysite:138] hi
[30/Oct/2018 12:50:06] INFO [mysite:144] ['hi']
[30/Oct/2018 12:50:22] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:50:27] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:50:27] INFO [mysite:138] hi
[30/Oct/2018 12:50:27] INFO [mysite:144] ['hi']
[30/Oct/2018 12:50:47] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:50:50] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:50:50] INFO [mysite:138] hi
[30/Oct/2018 12:50:50] INFO [mysite:144] ['hi']
[30/Oct/2018 12:50:50] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:52:04] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:52:07] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:52:07] INFO [mysite:138] hi
[30/Oct/2018 12:52:07] INFO [mysite:144] ['hi']
[30/Oct/2018 12:52:07] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:52:17] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:52:20] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:52:20] INFO [mysite:138] hi
[30/Oct/2018 12:52:20] INFO [mysite:144] ['hi']
[30/Oct/2018 12:52:20] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:52:43] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:52:46] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:52:46] INFO [mysite:138] hi
[30/Oct/2018 12:52:46] INFO [mysite:144] ['hi']
[30/Oct/2018 12:52:46] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:52:46] INFO [mysite:151] second
[30/Oct/2018 12:53:44] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:53:47] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:53:47] INFO [mysite:138] hi
[30/Oct/2018 12:53:47] INFO [mysite:144] ['hi']
[30/Oct/2018 12:53:47] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:53:47] INFO [mysite:151] second
[30/Oct/2018 12:53:47] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:53:57] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:07] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:07] INFO [mysite:138] hi
[30/Oct/2018 12:54:07] INFO [mysite:144] ['hi']
[30/Oct/2018 12:54:07] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:54:07] INFO [mysite:151] second
[30/Oct/2018 12:54:07] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:14] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:14] INFO [mysite:138] hi
[30/Oct/2018 12:54:14] INFO [mysite:144] ['test']
[30/Oct/2018 12:54:14] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:54:14] INFO [mysite:151] second
[30/Oct/2018 12:54:14] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:38] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:38] INFO [mysite:138] hi
[30/Oct/2018 12:54:38] INFO [mysite:144] ['test']
[30/Oct/2018 12:54:38] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:54:38] INFO [mysite:151] second
[30/Oct/2018 12:54:38] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:54] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:57] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:54:57] INFO [mysite:138] hi
[30/Oct/2018 12:54:57] INFO [mysite:144] ['hi']
[30/Oct/2018 12:54:57] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:54:57] INFO [mysite:151] second
[30/Oct/2018 12:55:32] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:55:34] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:55:34] INFO [mysite:138] hi
[30/Oct/2018 12:55:34] INFO [mysite:144] ['hi']
[30/Oct/2018 12:55:34] INFO [mysite:149] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:55:53] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:55:55] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:55:55] INFO [mysite:138] hi
[30/Oct/2018 12:55:55] INFO [mysite:144] ['hi']
[30/Oct/2018 12:55:55] INFO [mysite:149] [<Tag: second>]
[30/Oct/2018 12:58:05] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:58:08] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:58:08] INFO [mysite:138] hi
[30/Oct/2018 12:58:08] INFO [mysite:144] ['hi']
[30/Oct/2018 12:58:08] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:58:08] INFO [mysite:150] [<Tag: second>]
[30/Oct/2018 12:58:57] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:59:00] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 12:59:00] INFO [mysite:138] hi
[30/Oct/2018 12:59:00] INFO [mysite:144] ['hi']
[30/Oct/2018 12:59:00] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 12:59:00] INFO [mysite:150] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:00:05] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:00:08] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:00:08] INFO [mysite:138] hi
[30/Oct/2018 13:00:08] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:00:08] INFO [mysite:150] now
[30/Oct/2018 13:00:16] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:00:19] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:00:19] INFO [mysite:138] hi
[30/Oct/2018 13:00:19] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:00:19] INFO [mysite:150] now
[30/Oct/2018 13:00:19] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:00:56] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:01:04] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:01:04] INFO [mysite:138] hi
[30/Oct/2018 13:01:04] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:01:48] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:01:51] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:01:51] INFO [mysite:138] hi
[30/Oct/2018 13:01:51] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:02:23] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:02:26] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:02:26] INFO [mysite:138] hi
[30/Oct/2018 13:02:26] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:02:35] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:02:35] INFO [mysite:138] hi
[30/Oct/2018 13:02:35] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:02:40] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:02:40] INFO [mysite:138] hi
[30/Oct/2018 13:02:40] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:04:12] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:04:16] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:04:16] INFO [mysite:138] hi
[30/Oct/2018 13:04:16] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:04:16] INFO [mysite:150] now
[30/Oct/2018 13:04:16] INFO [mysite:150] now
[30/Oct/2018 13:04:16] INFO [mysite:150] now
[30/Oct/2018 13:04:16] INFO [mysite:150] now
[30/Oct/2018 13:04:16] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:17] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:18] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:19] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:04:20] INFO [mysite:150] now
[30/Oct/2018 13:05:10] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:05:13] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:05:13] INFO [mysite:138] hi
[30/Oct/2018 13:05:13] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:05:13] INFO [mysite:150] now
[30/Oct/2018 13:05:34] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:05:38] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:05:38] INFO [mysite:138] hi
[30/Oct/2018 13:05:38] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:05:38] INFO [mysite:150] now
[30/Oct/2018 13:06:12] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:06:12] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:06:16] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:06:16] INFO [mysite:138] hi
[30/Oct/2018 13:06:16] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:06:16] INFO [mysite:150] now
[30/Oct/2018 13:06:16] INFO [mysite:150] now
[30/Oct/2018 13:06:16] INFO [mysite:150] now
[30/Oct/2018 13:06:16] INFO [mysite:150] now
[30/Oct/2018 13:06:16] INFO [mysite:150] now
[30/Oct/2018 13:06:16] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:17] INFO [mysite:150] now
[30/Oct/2018 13:06:45] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:06:48] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:06:48] INFO [mysite:138] hi
[30/Oct/2018 13:06:49] INFO [mysite:148] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:07:30] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:07:33] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:07:33] INFO [mysite:138] hi
[30/Oct/2018 13:09:13] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:09:16] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:09:16] INFO [mysite:138] hi
[30/Oct/2018 13:11:17] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:11:21] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:11:21] INFO [mysite:138] hi
[30/Oct/2018 13:12:34] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:12:37] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:12:37] INFO [mysite:138] hi
[30/Oct/2018 13:12:58] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:13:01] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:13:01] INFO [mysite:138] hi
[30/Oct/2018 13:15:34] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:15:37] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:15:37] INFO [mysite:138] hi
[30/Oct/2018 13:15:58] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:16:02] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:16:02] INFO [mysite:138] hi
[30/Oct/2018 13:16:02] INFO [mysite:146] {'m_tags': ['test']}
[30/Oct/2018 13:16:39] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:16:42] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:16:42] INFO [mysite:138] hi
[30/Oct/2018 13:16:42] INFO [mysite:146] ['test']
[30/Oct/2018 13:17:06] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:17:09] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:17:09] INFO [mysite:138] hi
[30/Oct/2018 13:17:09] INFO [mysite:146] ['test']
[30/Oct/2018 13:20:47] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:20:51] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:20:51] INFO [mysite:138] hi
[30/Oct/2018 13:20:51] INFO [mysite:146] ['test']
[30/Oct/2018 13:21:14] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:21:16] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:21:16] INFO [mysite:138] hi
[30/Oct/2018 13:21:16] INFO [mysite:146] ['test']
[30/Oct/2018 13:21:16] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:27:04] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:27:08] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:27:08] INFO [mysite:138] hi
[30/Oct/2018 13:29:36] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:29:39] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:29:39] INFO [mysite:138] hi
[30/Oct/2018 13:29:39] INFO [mysite:146] ['test']
[30/Oct/2018 13:29:39] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:30:53] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:30:53] INFO [mysite:138] hi
[30/Oct/2018 13:30:53] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, '...(remaining elements truncated)...']>
[30/Oct/2018 13:30:53] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:30:56] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:30:56] INFO [mysite:138] hi
[30/Oct/2018 13:30:56] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, '...(remaining elements truncated)...']>
[30/Oct/2018 13:30:56] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: [>, <Tag: <>, <Tag: T>, <Tag: a>, <Tag: g>, <Tag: :>, <Tag: >, <Tag: h>, <Tag: i>, <Tag: >>, <Tag: ,>, <Tag: f>, <Tag: r>, <Tag: s>, <Tag: t>, <Tag: e>, <Tag: b>, <Tag: y>, <Tag: n>, <Tag: c>, <Tag: o>, <Tag: d>, <Tag: ]>]
[30/Oct/2018 13:31:51] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:31:54] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:31:54] INFO [mysite:138] hi
[30/Oct/2018 13:31:54] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:31:54] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:33:15] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:33:17] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:33:17] INFO [mysite:138] hi
[30/Oct/2018 13:33:17] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:33:17] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:33:52] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:33:55] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:33:55] INFO [mysite:138] hi
[30/Oct/2018 13:33:55] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:33:55] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:37:06] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:37:09] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:37:09] INFO [mysite:138] hi
[30/Oct/2018 13:38:31] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:38:34] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:38:34] INFO [mysite:138] hi
[30/Oct/2018 13:38:34] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:38:34] INFO [mysite:149] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>, <Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:38:54] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:38:57] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:38:57] INFO [mysite:138] hi
[30/Oct/2018 13:38:57] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:38:57] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:39:21] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:39:25] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:39:25] INFO [mysite:138] hi
[30/Oct/2018 13:39:25] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:39:25] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:39:40] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:39:43] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:39:43] INFO [mysite:138] hi
[30/Oct/2018 13:39:43] INFO [mysite:146] <QuerySet [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]>
[30/Oct/2018 13:39:43] INFO [mysite:147] [<Tag: hi>, <Tag: first>, <Tag: test>, <Tag: bayern>, <Tag: second>]
[30/Oct/2018 13:40:10] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:40:12] INFO [mysite:135] CustomUser object (2)
[30/Oct/2018 13:40:12] INFO [mysite:138] hi
[30/Oct/2018 13:40:34] INFO [mysite:135] CustomUser object (2)
Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

+10 -10
View File
@@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
import os
import re
import socket
import djcelery
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -47,8 +46,9 @@ INSTALLED_APPS = [
'application',
'taggit',
'taggit_templatetags2',
'djcelery',
'kombu.transport.django',
'post_office',
'hitcount',
]
MIDDLEWARE = [
@@ -254,11 +254,11 @@ if DEBUG:
'INTERCEPT_REDIRECTS': False,
}
# Celery settings
BROKER_URL = 'django://'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend'
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler"
djcelery.setup_loader()
EMAIL_BACKEND = 'post_office.EmailBackend'
EMAIL_HOST = 'smtp.web.de'
EMAIL_HOST_USER = "esther.kleinhenz@web.de"
EMAIL_PORT = 25 # default smtp port
EMAIL_HOST_PASSWORD = "2mSchneeinMikkeli"
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = 'your.generic.test.email@web.de'
+36
View File
@@ -0,0 +1,36 @@
altgraph==0.10.2
astroid==1.6.4
autopep8==1.3.5
backports-abc==0.5
backports.functools-lru-cache==1.5
bdist-mpkg==0.5.0
configparser==3.5.0
Django==1.11.15
django-classy-tags==0.8.0
django-taggit==0.23.0
django-taggit-templatetags2==1.6.1
enum34==1.1.6
futures==3.1.1
isort==4.3.4
lazy-object-proxy==1.3.1
macholib==1.5.1
mccabe==0.6.1
modulegraph==0.10.4
mysql-connector-python==8.0.12
nose==1.3.7
numpy==1.8.2
protobuf==3.6.1
py2app==0.7.3
pyasn1==0.4.4
pyasn1-modules==0.2.2
pycodestyle==2.4.0
pylint==1.9.1
pyparsing==2.0.1
python-dateutil==1.5
python-ldap==3.1.0
pytz==2013.7
scipy==1.1.0
singledispatch==3.4.0.3
six==1.11.0
tornado==5.1
wrapt==1.10.11
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Start Gunicorn processes
python3 manage.py runserver
-11
View File
@@ -1,11 +0,0 @@
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from celery.__main__ import _compat_worker
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(_compat_worker())
@@ -4,8 +4,8 @@
import re
import sys
from celery.__main__ import main
from gunicorn.app.wsgiapp import run
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
sys.exit(run())
@@ -4,8 +4,8 @@
import re
import sys
from celery.__main__ import _compat_beat
from gunicorn.app.djangoapp import run
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(_compat_beat())
sys.exit(run())
@@ -4,8 +4,8 @@
import re
import sys
from celery.__main__ import _compat_multi
from gunicorn.app.pasterapp import run
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(_compat_multi())
sys.exit(run())
BIN
View File
Binary file not shown.
@@ -0,0 +1,46 @@
Django is a high-level Python Web framework that encourages rapid development
and clean, pragmatic design. Thanks for checking it out.
All documentation is in the "``docs``" directory and online at
https://docs.djangoproject.com/en/stable/. If you're just getting started,
here's how we recommend you read the docs:
* First, read ``docs/intro/install.txt`` for instructions on installing Django.
* Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
``docs/intro/tutorial02.txt``, etc.).
* If you want to set up an actual deployment server, read
``docs/howto/deployment/index.txt`` for instructions.
* You'll probably want to read through the topical guides (in ``docs/topics``)
next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
problems, and check out the reference (``docs/ref``) for gory details.
* See ``docs/README`` for instructions on building an HTML version of the docs.
Docs are updated rigorously. If you find any problems in the docs, or think
they should be clarified in any way, please take 30 seconds to fill out a
ticket here: https://code.djangoproject.com/newticket
To get more help:
* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang out
there. Read the archives at https://botbot.me/freenode/django/. See
https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're new to IRC.
* Join the django-users mailing list, or read the archives, at
https://groups.google.com/group/django-users.
To contribute to Django:
* Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
information about getting involved.
To run Django's test suite:
* Follow the instructions in the "Unit tests" section of
``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Metadata-Version: 2.0
Name: Django
Version: 2.1
Version: 2.1.2
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
@@ -10,6 +10,7 @@ Project-URL: Documentation, https://docs.djangoproject.com/
Project-URL: Funding, https://www.djangoproject.com/fundraising/
Project-URL: Source, https://github.com/django/django
Project-URL: Tracker, https://code.djangoproject.com/
Description-Content-Type: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
@@ -29,8 +30,6 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.5
Provides-Extra: argon2
Provides-Extra: bcrypt
Requires-Dist: pytz
Provides-Extra: argon2
Requires-Dist: argon2-cffi (>=16.1.0); extra == 'argon2'
@@ -1,5 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.31.1)
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: true
Tag: py3-none-any
@@ -0,0 +1 @@
{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3 :: Only", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "description_content_type": "UNKNOWN", "extensions": {"python.commands": {"wrap_console": {"django-admin": "django.core.management:execute_from_command_line"}}, "python.details": {"contacts": [{"email": "foundation@djangoproject.com", "name": "Django Software Foundation", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "https://www.djangoproject.com/"}}, "python.exports": {"console_scripts": {"django-admin": "django.core.management:execute_from_command_line"}}}, "extras": ["argon2", "bcrypt"], "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "Django", "project_url": "Documentation, https://docs.djangoproject.com/", "requires_python": ">=3.5", "run_requires": [{"extra": "argon2", "requires": ["argon2-cffi (>=16.1.0)"]}, {"extra": "bcrypt", "requires": ["bcrypt"]}, {"requires": ["pytz"]}], "summary": "A high-level Python Web framework that encourages rapid development and clean, pragmatic design.", "version": "2.1.2"}
@@ -1,155 +0,0 @@
# -*- coding: utf-8 -*-
"""Distributed Task Queue"""
# :copyright: (c) 2015 Ask Solem and individual contributors.
# All rights # reserved.
# :copyright: (c) 2012-2014 GoPivotal, Inc., All rights reserved.
# :copyright: (c) 2009 - 2012 Ask Solem and individual contributors,
# All rights reserved.
# :license: BSD (3 Clause), see LICENSE for more details.
from __future__ import absolute_import
import os
import sys
from collections import namedtuple
version_info_t = namedtuple(
'version_info_t', ('major', 'minor', 'micro', 'releaselevel', 'serial'),
)
SERIES = 'Cipater'
VERSION = version_info_t(3, 1, 26, '.post2', '')
__version__ = '{0.major}.{0.minor}.{0.micro}{0.releaselevel}'.format(VERSION)
__author__ = 'Ask Solem'
__contact__ = 'ask@celeryproject.org'
__homepage__ = 'http://celeryproject.org'
__docformat__ = 'restructuredtext'
__all__ = [
'Celery', 'bugreport', 'shared_task', 'task',
'current_app', 'current_task', 'maybe_signature',
'chain', 'chord', 'chunks', 'group', 'signature',
'xmap', 'xstarmap', 'uuid', 'version', '__version__',
]
VERSION_BANNER = '{0} ({1})'.format(__version__, SERIES)
# -eof meta-
if os.environ.get('C_IMPDEBUG'): # pragma: no cover
from .five import builtins
real_import = builtins.__import__
def debug_import(name, locals=None, globals=None,
fromlist=None, level=-1):
glob = globals or getattr(sys, 'emarfteg_'[::-1])(1).f_globals
importer_name = glob and glob.get('__name__') or 'unknown'
print('-- {0} imports {1}'.format(importer_name, name))
return real_import(name, locals, globals, fromlist, level)
builtins.__import__ = debug_import
# This is never executed, but tricks static analyzers (PyDev, PyCharm,
# pylint, etc.) into knowing the types of these symbols, and what
# they contain.
STATICA_HACK = True
globals()['kcah_acitats'[::-1].upper()] = False
if STATICA_HACK: # pragma: no cover
from celery.app import shared_task # noqa
from celery.app.base import Celery # noqa
from celery.app.utils import bugreport # noqa
from celery.app.task import Task # noqa
from celery._state import current_app, current_task # noqa
from celery.canvas import ( # noqa
chain, chord, chunks, group,
signature, maybe_signature, xmap, xstarmap, subtask,
)
from celery.utils import uuid # noqa
# Eventlet/gevent patching must happen before importing
# anything else, so these tools must be at top-level.
def _find_option_with_arg(argv, short_opts=None, long_opts=None):
"""Search argv for option specifying its short and longopt
alternatives.
Return the value of the option if found.
"""
for i, arg in enumerate(argv):
if arg.startswith('-'):
if long_opts and arg.startswith('--'):
name, _, val = arg.partition('=')
if name in long_opts:
return val
if short_opts and arg in short_opts:
return argv[i + 1]
raise KeyError('|'.join(short_opts or [] + long_opts or []))
def _patch_eventlet():
import eventlet
import eventlet.debug
eventlet.monkey_patch()
EVENTLET_DBLOCK = int(os.environ.get('EVENTLET_NOBLOCK', 0))
if EVENTLET_DBLOCK:
eventlet.debug.hub_blocking_detection(EVENTLET_DBLOCK)
def _patch_gevent():
from gevent import monkey, version_info
monkey.patch_all()
if version_info[0] == 0: # pragma: no cover
# Signals aren't working in gevent versions <1.0,
# and are not monkey patched by patch_all()
from gevent import signal as _gevent_signal
_signal = __import__('signal')
_signal.signal = _gevent_signal
def maybe_patch_concurrency(argv=sys.argv,
short_opts=['-P'], long_opts=['--pool'],
patches={'eventlet': _patch_eventlet,
'gevent': _patch_gevent}):
"""With short and long opt alternatives that specify the command line
option to set the pool, this makes sure that anything that needs
to be patched is completed as early as possible.
(e.g. eventlet/gevent monkey patches)."""
try:
pool = _find_option_with_arg(argv, short_opts, long_opts)
except KeyError:
pass
else:
try:
patcher = patches[pool]
except KeyError:
pass
else:
patcher()
# set up eventlet/gevent environments ASAP.
from celery import concurrency
concurrency.get_implementation(pool)
# Lazy loading
from celery import five # noqa
old_module, new_module = five.recreate_module( # pragma: no cover
__name__,
by_module={
'celery.app': ['Celery', 'bugreport', 'shared_task'],
'celery.app.task': ['Task'],
'celery._state': ['current_app', 'current_task'],
'celery.canvas': ['chain', 'chord', 'chunks', 'group',
'signature', 'maybe_signature', 'subtask',
'xmap', 'xstarmap'],
'celery.utils': ['uuid'],
},
direct={'task': 'celery.task'},
__package__='celery', __file__=__file__,
__path__=__path__, __doc__=__doc__, __version__=__version__,
__author__=__author__, __contact__=__contact__,
__homepage__=__homepage__, __docformat__=__docformat__, five=five,
VERSION=VERSION, SERIES=SERIES, VERSION_BANNER=VERSION_BANNER,
version_info_t=version_info_t,
maybe_patch_concurrency=maybe_patch_concurrency,
_find_option_with_arg=_find_option_with_arg,
)
@@ -1,54 +0,0 @@
from __future__ import absolute_import
import sys
from os.path import basename
from . import maybe_patch_concurrency
__all__ = ['main']
DEPRECATED_FMT = """
The {old!r} command is deprecated, please use {new!r} instead:
$ {new_argv}
"""
def _warn_deprecated(new):
print(DEPRECATED_FMT.format(
old=basename(sys.argv[0]), new=new,
new_argv=' '.join([new] + sys.argv[1:])),
)
def main():
if 'multi' not in sys.argv:
maybe_patch_concurrency()
from celery.bin.celery import main
main()
def _compat_worker():
maybe_patch_concurrency()
_warn_deprecated('celery worker')
from celery.bin.worker import main
main()
def _compat_multi():
_warn_deprecated('celery multi')
from celery.bin.multi import main
main()
def _compat_beat():
maybe_patch_concurrency()
_warn_deprecated('celery beat')
from celery.bin.beat import main
main()
if __name__ == '__main__': # pragma: no cover
main()
@@ -1,159 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery._state
~~~~~~~~~~~~~~~
This is an internal module containing thread state
like the ``current_app``, and ``current_task``.
This module shouldn't be used directly.
"""
from __future__ import absolute_import, print_function
import os
import sys
import threading
import weakref
from celery.local import Proxy
from celery.utils.threads import LocalStack
try:
from weakref import WeakSet as AppSet
except ImportError: # XXX Py2.6
class AppSet(object): # noqa
def __init__(self):
self._refs = set()
def add(self, app):
self._refs.add(weakref.ref(app))
def __iter__(self):
dirty = []
try:
for appref in self._refs:
app = appref()
if app is None:
dirty.append(appref)
else:
yield app
finally:
while dirty:
self._refs.discard(dirty.pop())
__all__ = ['set_default_app', 'get_current_app', 'get_current_task',
'get_current_worker_task', 'current_app', 'current_task',
'connect_on_app_finalize']
#: Global default app used when no current app.
default_app = None
#: List of all app instances (weakrefs), must not be used directly.
_apps = AppSet()
#: global set of functions to call whenever a new app is finalized
#: E.g. Shared tasks, and builtin tasks are created
#: by adding callbacks here.
_on_app_finalizers = set()
_task_join_will_block = False
def connect_on_app_finalize(callback):
_on_app_finalizers.add(callback)
return callback
def _announce_app_finalized(app):
callbacks = set(_on_app_finalizers)
for callback in callbacks:
callback(app)
def _set_task_join_will_block(blocks):
global _task_join_will_block
_task_join_will_block = blocks
def task_join_will_block():
return _task_join_will_block
class _TLS(threading.local):
#: Apps with the :attr:`~celery.app.base.BaseApp.set_as_current` attribute
#: sets this, so it will always contain the last instantiated app,
#: and is the default app returned by :func:`app_or_default`.
current_app = None
_tls = _TLS()
_task_stack = LocalStack()
def set_default_app(app):
global default_app
default_app = app
def _get_current_app():
if default_app is None:
#: creates the global fallback app instance.
from celery.app import Celery
set_default_app(Celery(
'default',
loader=os.environ.get('CELERY_LOADER') or 'default',
fixups=[],
set_as_current=False, accept_magic_kwargs=True,
))
return _tls.current_app or default_app
def _set_current_app(app):
_tls.current_app = app
C_STRICT_APP = os.environ.get('C_STRICT_APP')
if os.environ.get('C_STRICT_APP'): # pragma: no cover
def get_current_app():
raise Exception('USES CURRENT APP')
import traceback
print('-- USES CURRENT_APP', file=sys.stderr) # noqa+
traceback.print_stack(file=sys.stderr)
return _get_current_app()
else:
get_current_app = _get_current_app
def get_current_task():
"""Currently executing task."""
return _task_stack.top
def get_current_worker_task():
"""Currently executing task, that was applied by the worker.
This is used to differentiate between the actual task
executed by the worker and any task that was called within
a task (using ``task.__call__`` or ``task.apply``)
"""
for task in reversed(_task_stack.stack):
if not task.request.called_directly:
return task
#: Proxy to current app.
current_app = Proxy(get_current_app)
#: Proxy to current task.
current_task = Proxy(get_current_task)
def _register_app(app):
_apps.add(app)
def _get_active_apps():
return _apps
@@ -1,150 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app
~~~~~~~~~~
Celery Application.
"""
from __future__ import absolute_import
import os
from celery.local import Proxy
from celery import _state
from celery._state import (
get_current_app as current_app,
get_current_task as current_task,
connect_on_app_finalize, set_default_app, _get_active_apps, _task_stack,
)
from celery.utils import gen_task_name
from .base import Celery, AppPickler
__all__ = ['Celery', 'AppPickler', 'default_app', 'app_or_default',
'bugreport', 'enable_trace', 'disable_trace', 'shared_task',
'set_default_app', 'current_app', 'current_task',
'push_current_task', 'pop_current_task']
#: Proxy always returning the app set as default.
default_app = Proxy(lambda: _state.default_app)
#: Function returning the app provided or the default app if none.
#:
#: The environment variable :envvar:`CELERY_TRACE_APP` is used to
#: trace app leaks. When enabled an exception is raised if there
#: is no active app.
app_or_default = None
#: The 'default' loader is the default loader used by old applications.
#: This is deprecated and should no longer be used as it's set too early
#: to be affected by --loader argument.
default_loader = os.environ.get('CELERY_LOADER') or 'default' # XXX
#: Function used to push a task to the thread local stack
#: keeping track of the currently executing task.
#: You must remember to pop the task after.
push_current_task = _task_stack.push
#: Function used to pop a task from the thread local stack
#: keeping track of the currently executing task.
pop_current_task = _task_stack.pop
def bugreport(app=None):
return (app or current_app()).bugreport()
def _app_or_default(app=None):
if app is None:
return _state.get_current_app()
return app
def _app_or_default_trace(app=None): # pragma: no cover
from traceback import print_stack
from billiard import current_process
if app is None:
if getattr(_state._tls, 'current_app', None):
print('-- RETURNING TO CURRENT APP --') # noqa+
print_stack()
return _state._tls.current_app
if current_process()._name == 'MainProcess':
raise Exception('DEFAULT APP')
print('-- RETURNING TO DEFAULT APP --') # noqa+
print_stack()
return _state.default_app
return app
def enable_trace():
global app_or_default
app_or_default = _app_or_default_trace
def disable_trace():
global app_or_default
app_or_default = _app_or_default
if os.environ.get('CELERY_TRACE_APP'): # pragma: no cover
enable_trace()
else:
disable_trace()
App = Celery # XXX Compat
def shared_task(*args, **kwargs):
"""Create shared tasks (decorator).
Will return a proxy that always takes the task from the current apps
task registry.
This can be used by library authors to create tasks that will work
for any app environment.
Example:
>>> from celery import Celery, shared_task
>>> @shared_task
... def add(x, y):
... return x + y
>>> app1 = Celery(broker='amqp://')
>>> add.app is app1
True
>>> app2 = Celery(broker='redis://')
>>> add.app is app2
"""
def create_shared_task(**options):
def __inner(fun):
name = options.get('name')
# Set as shared task so that unfinalized apps,
# and future apps will load the task.
connect_on_app_finalize(
lambda app: app._task_from_fun(fun, **options)
)
# Force all finalized apps to take this task as well.
for app in _get_active_apps():
if app.finalized:
with app._finalize_mutex:
app._task_from_fun(fun, **options)
# Return a proxy that always gets the task from the current
# apps task registry.
def task_by_cons():
app = current_app()
return app.tasks[
name or gen_task_name(app, fun.__name__, fun.__module__)
]
return Proxy(task_by_cons)
return __inner
if len(args) == 1 and callable(args[0]):
return create_shared_task(**kwargs)(args[0])
return create_shared_task(*args, **kwargs)
@@ -1,512 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.amqp
~~~~~~~~~~~~~~~
Sending and receiving messages using Kombu.
"""
from __future__ import absolute_import
import numbers
from datetime import timedelta
from weakref import WeakValueDictionary
from kombu import Connection, Consumer, Exchange, Producer, Queue
from kombu.common import Broadcast
from kombu.pools import ProducerPool
from kombu.utils import cached_property, uuid
from kombu.utils.encoding import safe_repr
from kombu.utils.functional import maybe_list
from celery import signals
from celery.five import items, string_t
from celery.utils.text import indent as textindent
from celery.utils.timeutils import to_utc
from . import app_or_default
from . import routes as _routes
__all__ = ['AMQP', 'Queues', 'TaskProducer', 'TaskConsumer']
#: earliest date supported by time.mktime.
INT_MIN = -2147483648
#: Human readable queue declaration.
QUEUE_FORMAT = """
.> {0.name:<16} exchange={0.exchange.name}({0.exchange.type}) \
key={0.routing_key}
"""
class Queues(dict):
"""Queue name⇒ declaration mapping.
:param queues: Initial list/tuple or dict of queues.
:keyword create_missing: By default any unknown queues will be
added automatically, but if disabled
the occurrence of unknown queues
in `wanted` will raise :exc:`KeyError`.
:keyword ha_policy: Default HA policy for queues with none set.
"""
#: If set, this is a subset of queues to consume from.
#: The rest of the queues are then used for routing only.
_consume_from = None
def __init__(self, queues=None, default_exchange=None,
create_missing=True, ha_policy=None, autoexchange=None):
dict.__init__(self)
self.aliases = WeakValueDictionary()
self.default_exchange = default_exchange
self.create_missing = create_missing
self.ha_policy = ha_policy
self.autoexchange = Exchange if autoexchange is None else autoexchange
if isinstance(queues, (tuple, list)):
queues = dict((q.name, q) for q in queues)
for name, q in items(queues or {}):
self.add(q) if isinstance(q, Queue) else self.add_compat(name, **q)
def __getitem__(self, name):
try:
return self.aliases[name]
except KeyError:
return dict.__getitem__(self, name)
def __setitem__(self, name, queue):
if self.default_exchange and (not queue.exchange or
not queue.exchange.name):
queue.exchange = self.default_exchange
dict.__setitem__(self, name, queue)
if queue.alias:
self.aliases[queue.alias] = queue
def __missing__(self, name):
if self.create_missing:
return self.add(self.new_missing(name))
raise KeyError(name)
def add(self, queue, **kwargs):
"""Add new queue.
The first argument can either be a :class:`kombu.Queue` instance,
or the name of a queue. If the former the rest of the keyword
arguments are ignored, and options are simply taken from the queue
instance.
:param queue: :class:`kombu.Queue` instance or name of the queue.
:keyword exchange: (if named) specifies exchange name.
:keyword routing_key: (if named) specifies binding key.
:keyword exchange_type: (if named) specifies type of exchange.
:keyword \*\*options: (if named) Additional declaration options.
"""
if not isinstance(queue, Queue):
return self.add_compat(queue, **kwargs)
if self.ha_policy:
if queue.queue_arguments is None:
queue.queue_arguments = {}
self._set_ha_policy(queue.queue_arguments)
self[queue.name] = queue
return queue
def add_compat(self, name, **options):
# docs used to use binding_key as routing key
options.setdefault('routing_key', options.get('binding_key'))
if options['routing_key'] is None:
options['routing_key'] = name
if self.ha_policy is not None:
self._set_ha_policy(options.setdefault('queue_arguments', {}))
q = self[name] = Queue.from_dict(name, **options)
return q
def _set_ha_policy(self, args):
policy = self.ha_policy
if isinstance(policy, (list, tuple)):
return args.update({'x-ha-policy': 'nodes',
'x-ha-policy-params': list(policy)})
args['x-ha-policy'] = policy
def format(self, indent=0, indent_first=True):
"""Format routing table into string for log dumps."""
active = self.consume_from
if not active:
return ''
info = [QUEUE_FORMAT.strip().format(q)
for _, q in sorted(items(active))]
if indent_first:
return textindent('\n'.join(info), indent)
return info[0] + '\n' + textindent('\n'.join(info[1:]), indent)
def select_add(self, queue, **kwargs):
"""Add new task queue that will be consumed from even when
a subset has been selected using the :option:`-Q` option."""
q = self.add(queue, **kwargs)
if self._consume_from is not None:
self._consume_from[q.name] = q
return q
def select(self, include):
"""Sets :attr:`consume_from` by selecting a subset of the
currently defined queues.
:param include: Names of queues to consume from.
Can be iterable or string.
"""
if include:
self._consume_from = dict((name, self[name])
for name in maybe_list(include))
select_subset = select # XXX compat
def deselect(self, exclude):
"""Deselect queues so that they will not be consumed from.
:param exclude: Names of queues to avoid consuming from.
Can be iterable or string.
"""
if exclude:
exclude = maybe_list(exclude)
if self._consume_from is None:
# using selection
return self.select(k for k in self if k not in exclude)
# using all queues
for queue in exclude:
self._consume_from.pop(queue, None)
select_remove = deselect # XXX compat
def new_missing(self, name):
return Queue(name, self.autoexchange(name), name)
@property
def consume_from(self):
if self._consume_from is not None:
return self._consume_from
return self
class TaskProducer(Producer):
app = None
auto_declare = False
retry = False
retry_policy = None
utc = True
event_dispatcher = None
send_sent_event = False
def __init__(self, channel=None, exchange=None, *args, **kwargs):
self.retry = kwargs.pop('retry', self.retry)
self.retry_policy = kwargs.pop('retry_policy',
self.retry_policy or {})
self.send_sent_event = kwargs.pop('send_sent_event',
self.send_sent_event)
exchange = exchange or self.exchange
self.queues = self.app.amqp.queues # shortcut
self.default_queue = self.app.amqp.default_queue
self._default_mode = self.app.conf.CELERY_DEFAULT_DELIVERY_MODE
super(TaskProducer, self).__init__(channel, exchange, *args, **kwargs)
def publish_task(self, task_name, task_args=None, task_kwargs=None,
countdown=None, eta=None, task_id=None, group_id=None,
taskset_id=None, # compat alias to group_id
expires=None, exchange=None, exchange_type=None,
event_dispatcher=None, retry=None, retry_policy=None,
queue=None, now=None, retries=0, chord=None,
callbacks=None, errbacks=None, routing_key=None,
serializer=None, delivery_mode=None, compression=None,
reply_to=None, time_limit=None, soft_time_limit=None,
declare=None, headers=None,
send_before_publish=signals.before_task_publish.send,
before_receivers=signals.before_task_publish.receivers,
send_after_publish=signals.after_task_publish.send,
after_receivers=signals.after_task_publish.receivers,
send_task_sent=signals.task_sent.send, # XXX deprecated
sent_receivers=signals.task_sent.receivers,
**kwargs):
"""Send task message."""
retry = self.retry if retry is None else retry
headers = {} if headers is None else headers
qname = queue
if queue is None and exchange is None:
queue = self.default_queue
if queue is not None:
if isinstance(queue, string_t):
qname, queue = queue, self.queues[queue]
else:
qname = queue.name
exchange = exchange or queue.exchange.name
routing_key = routing_key or queue.routing_key
if declare is None and queue and not isinstance(queue, Broadcast):
declare = [queue]
if delivery_mode is None:
delivery_mode = self._default_mode
# merge default and custom policy
retry = self.retry if retry is None else retry
_rp = (dict(self.retry_policy, **retry_policy) if retry_policy
else self.retry_policy)
task_id = task_id or uuid()
task_args = task_args or []
task_kwargs = task_kwargs or {}
if not isinstance(task_args, (list, tuple)):
raise ValueError('task args must be a list or tuple')
if not isinstance(task_kwargs, dict):
raise ValueError('task kwargs must be a dictionary')
if countdown: # Convert countdown to ETA.
self._verify_seconds(countdown, 'countdown')
now = now or self.app.now()
eta = now + timedelta(seconds=countdown)
if self.utc:
eta = to_utc(eta).astimezone(self.app.timezone)
if isinstance(expires, numbers.Real):
self._verify_seconds(expires, 'expires')
now = now or self.app.now()
expires = now + timedelta(seconds=expires)
if self.utc:
expires = to_utc(expires).astimezone(self.app.timezone)
eta = eta and eta.isoformat()
expires = expires and expires.isoformat()
body = {
'task': task_name,
'id': task_id,
'args': task_args,
'kwargs': task_kwargs,
'retries': retries or 0,
'eta': eta,
'expires': expires,
'utc': self.utc,
'callbacks': callbacks,
'errbacks': errbacks,
'timelimit': (time_limit, soft_time_limit),
'taskset': group_id or taskset_id,
'chord': chord,
}
if before_receivers:
send_before_publish(
sender=task_name, body=body,
exchange=exchange,
routing_key=routing_key,
declare=declare,
headers=headers,
properties=kwargs,
retry_policy=retry_policy,
)
self.publish(
body,
exchange=exchange, routing_key=routing_key,
serializer=serializer or self.serializer,
compression=compression or self.compression,
headers=headers,
retry=retry, retry_policy=_rp,
reply_to=reply_to,
correlation_id=task_id,
delivery_mode=delivery_mode, declare=declare,
**kwargs
)
if after_receivers:
send_after_publish(sender=task_name, body=body,
exchange=exchange, routing_key=routing_key)
if sent_receivers: # XXX deprecated
send_task_sent(sender=task_name, task_id=task_id,
task=task_name, args=task_args,
kwargs=task_kwargs, eta=eta,
taskset=group_id or taskset_id)
if self.send_sent_event:
evd = event_dispatcher or self.event_dispatcher
exname = exchange or self.exchange
if isinstance(exname, Exchange):
exname = exname.name
evd.publish(
'task-sent',
{
'uuid': task_id,
'name': task_name,
'args': safe_repr(task_args),
'kwargs': safe_repr(task_kwargs),
'retries': retries,
'eta': eta,
'expires': expires,
'queue': qname,
'exchange': exname,
'routing_key': routing_key,
},
self, retry=retry, retry_policy=retry_policy,
)
return task_id
delay_task = publish_task # XXX Compat
def _verify_seconds(self, s, what):
if s < INT_MIN:
raise ValueError('%s is out of range: %r' % (what, s))
return s
@cached_property
def event_dispatcher(self):
# We call Dispatcher.publish with a custom producer
# so don't need the dispatcher to be "enabled".
return self.app.events.Dispatcher(enabled=False)
class TaskPublisher(TaskProducer):
"""Deprecated version of :class:`TaskProducer`."""
def __init__(self, channel=None, exchange=None, *args, **kwargs):
self.app = app_or_default(kwargs.pop('app', self.app))
self.retry = kwargs.pop('retry', self.retry)
self.retry_policy = kwargs.pop('retry_policy',
self.retry_policy or {})
exchange = exchange or self.exchange
if not isinstance(exchange, Exchange):
exchange = Exchange(exchange,
kwargs.pop('exchange_type', 'direct'))
self.queues = self.app.amqp.queues # shortcut
super(TaskPublisher, self).__init__(channel, exchange, *args, **kwargs)
class TaskConsumer(Consumer):
app = None
def __init__(self, channel, queues=None, app=None, accept=None, **kw):
self.app = app or self.app
if accept is None:
accept = self.app.conf.CELERY_ACCEPT_CONTENT
super(TaskConsumer, self).__init__(
channel,
queues or list(self.app.amqp.queues.consume_from.values()),
accept=accept,
**kw
)
class AMQP(object):
Connection = Connection
Consumer = Consumer
#: compat alias to Connection
BrokerConnection = Connection
producer_cls = TaskProducer
consumer_cls = TaskConsumer
queues_cls = Queues
#: Cached and prepared routing table.
_rtable = None
#: Underlying producer pool instance automatically
#: set by the :attr:`producer_pool`.
_producer_pool = None
# Exchange class/function used when defining automatic queues.
# E.g. you can use ``autoexchange = lambda n: None`` to use the
# amqp default exchange, which is a shortcut to bypass routing
# and instead send directly to the queue named in the routing key.
autoexchange = None
def __init__(self, app):
self.app = app
def flush_routes(self):
self._rtable = _routes.prepare(self.app.conf.CELERY_ROUTES)
def Queues(self, queues, create_missing=None, ha_policy=None,
autoexchange=None):
"""Create new :class:`Queues` instance, using queue defaults
from the current configuration."""
conf = self.app.conf
if create_missing is None:
create_missing = conf.CELERY_CREATE_MISSING_QUEUES
if ha_policy is None:
ha_policy = conf.CELERY_QUEUE_HA_POLICY
if not queues and conf.CELERY_DEFAULT_QUEUE:
queues = (Queue(conf.CELERY_DEFAULT_QUEUE,
exchange=self.default_exchange,
routing_key=conf.CELERY_DEFAULT_ROUTING_KEY), )
autoexchange = (self.autoexchange if autoexchange is None
else autoexchange)
return self.queues_cls(
queues, self.default_exchange, create_missing,
ha_policy, autoexchange,
)
def Router(self, queues=None, create_missing=None):
"""Return the current task router."""
return _routes.Router(self.routes, queues or self.queues,
self.app.either('CELERY_CREATE_MISSING_QUEUES',
create_missing), app=self.app)
@cached_property
def TaskConsumer(self):
"""Return consumer configured to consume from the queues
we are configured for (``app.amqp.queues.consume_from``)."""
return self.app.subclass_with_self(self.consumer_cls,
reverse='amqp.TaskConsumer')
get_task_consumer = TaskConsumer # XXX compat
@cached_property
def TaskProducer(self):
"""Return publisher used to send tasks.
You should use `app.send_task` instead.
"""
conf = self.app.conf
return self.app.subclass_with_self(
self.producer_cls,
reverse='amqp.TaskProducer',
exchange=self.default_exchange,
routing_key=conf.CELERY_DEFAULT_ROUTING_KEY,
serializer=conf.CELERY_TASK_SERIALIZER,
compression=conf.CELERY_MESSAGE_COMPRESSION,
retry=conf.CELERY_TASK_PUBLISH_RETRY,
retry_policy=conf.CELERY_TASK_PUBLISH_RETRY_POLICY,
send_sent_event=conf.CELERY_SEND_TASK_SENT_EVENT,
utc=conf.CELERY_ENABLE_UTC,
)
TaskPublisher = TaskProducer # compat
@cached_property
def default_queue(self):
return self.queues[self.app.conf.CELERY_DEFAULT_QUEUE]
@cached_property
def queues(self):
"""Queue name⇒ declaration mapping."""
return self.Queues(self.app.conf.CELERY_QUEUES)
@queues.setter # noqa
def queues(self, queues):
return self.Queues(queues)
@property
def routes(self):
if self._rtable is None:
self.flush_routes()
return self._rtable
@cached_property
def router(self):
return self.Router()
@property
def producer_pool(self):
if self._producer_pool is None:
self._producer_pool = ProducerPool(
self.app.pool,
limit=self.app.pool.limit,
Producer=self.TaskProducer,
)
return self._producer_pool
publisher_pool = producer_pool # compat alias
@cached_property
def default_exchange(self):
return Exchange(self.app.conf.CELERY_DEFAULT_EXCHANGE,
self.app.conf.CELERY_DEFAULT_EXCHANGE_TYPE)
@@ -1,58 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.annotations
~~~~~~~~~~~~~~~~~~~~~~
Annotations is a nice term for monkey patching
task classes in the configuration.
This prepares and performs the annotations in the
:setting:`CELERY_ANNOTATIONS` setting.
"""
from __future__ import absolute_import
from celery.five import string_t
from celery.utils.functional import firstmethod, mlazy
from celery.utils.imports import instantiate
_first_match = firstmethod('annotate')
_first_match_any = firstmethod('annotate_any')
__all__ = ['MapAnnotation', 'prepare', 'resolve_all']
class MapAnnotation(dict):
def annotate_any(self):
try:
return dict(self['*'])
except KeyError:
pass
def annotate(self, task):
try:
return dict(self[task.name])
except KeyError:
pass
def prepare(annotations):
"""Expands the :setting:`CELERY_ANNOTATIONS` setting."""
def expand_annotation(annotation):
if isinstance(annotation, dict):
return MapAnnotation(annotation)
elif isinstance(annotation, string_t):
return mlazy(instantiate, annotation)
return annotation
if annotations is None:
return ()
elif not isinstance(annotations, (list, tuple)):
annotations = (annotations, )
return [expand_annotation(anno) for anno in annotations]
def resolve_all(anno, task):
return (x for x in (_first_match(anno, task), _first_match_any(anno)) if x)
@@ -1,675 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.base
~~~~~~~~~~~~~~~
Actual App instance implementation.
"""
from __future__ import absolute_import
import os
import threading
import warnings
from collections import defaultdict, deque
from copy import deepcopy
from operator import attrgetter
from amqp import promise
from billiard.util import register_after_fork
from kombu.clocks import LamportClock
from kombu.common import oid_from
from kombu.utils import cached_property, uuid
from celery import platforms
from celery import signals
from celery._state import (
_task_stack, get_current_app, _set_current_app, set_default_app,
_register_app, get_current_worker_task, connect_on_app_finalize,
_announce_app_finalized,
)
from celery.exceptions import AlwaysEagerIgnored, ImproperlyConfigured
from celery.five import values
from celery.loaders import get_loader_cls
from celery.local import PromiseProxy, maybe_evaluate
from celery.utils.functional import first, maybe_list
from celery.utils.imports import instantiate, symbol_by_name
from celery.utils.objects import FallbackContext, mro_lookup
from .annotations import prepare as prepare_annotations
from .defaults import DEFAULTS, find_deprecated_settings
from .registry import TaskRegistry
from .utils import (
AppPickler, Settings, bugreport, _unpickle_app, _unpickle_app_v2, appstr,
)
# Load all builtin tasks
from . import builtins # noqa
__all__ = ['Celery']
_EXECV = os.environ.get('FORKED_BY_MULTIPROCESSING')
BUILTIN_FIXUPS = frozenset([
'celery.fixups.django:fixup',
])
ERR_ENVVAR_NOT_SET = """\
The environment variable {0!r} is not set,
and as such the configuration could not be loaded.
Please set this variable and make it point to
a configuration module."""
_after_fork_registered = False
def app_has_custom(app, attr):
return mro_lookup(app.__class__, attr, stop=(Celery, object),
monkey_patched=[__name__])
def _unpickle_appattr(reverse_name, args):
"""Given an attribute name and a list of args, gets
the attribute from the current app and calls it."""
return get_current_app()._rgetattr(reverse_name)(*args)
def _global_after_fork(obj):
# Previously every app would call:
# `register_after_fork(app, app._after_fork)`
# but this created a leak as `register_after_fork` stores concrete object
# references and once registered an object cannot be removed without
# touching and iterating over the private afterfork registry list.
#
# See Issue #1949
from celery import _state
from multiprocessing import util as mputil
for app in _state._apps:
try:
app._after_fork(obj)
except Exception as exc:
if mputil._logger:
mputil._logger.info(
'after forker raised exception: %r', exc, exc_info=1)
def _ensure_after_fork():
global _after_fork_registered
_after_fork_registered = True
register_after_fork(_global_after_fork, _global_after_fork)
class Celery(object):
#: This is deprecated, use :meth:`reduce_keys` instead
Pickler = AppPickler
SYSTEM = platforms.SYSTEM
IS_OSX, IS_WINDOWS = platforms.IS_OSX, platforms.IS_WINDOWS
amqp_cls = 'celery.app.amqp:AMQP'
backend_cls = None
events_cls = 'celery.events:Events'
loader_cls = 'celery.loaders.app:AppLoader'
log_cls = 'celery.app.log:Logging'
control_cls = 'celery.app.control:Control'
task_cls = 'celery.app.task:Task'
registry_cls = TaskRegistry
_fixups = None
_pool = None
builtin_fixups = BUILTIN_FIXUPS
def __init__(self, main=None, loader=None, backend=None,
amqp=None, events=None, log=None, control=None,
set_as_current=True, accept_magic_kwargs=False,
tasks=None, broker=None, include=None, changes=None,
config_source=None, fixups=None, task_cls=None,
autofinalize=True, **kwargs):
self.clock = LamportClock()
self.main = main
self.amqp_cls = amqp or self.amqp_cls
self.events_cls = events or self.events_cls
self.loader_cls = loader or self.loader_cls
self.log_cls = log or self.log_cls
self.control_cls = control or self.control_cls
self.task_cls = task_cls or self.task_cls
self.set_as_current = set_as_current
self.registry_cls = symbol_by_name(self.registry_cls)
self.accept_magic_kwargs = accept_magic_kwargs
self.user_options = defaultdict(set)
self.steps = defaultdict(set)
self.autofinalize = autofinalize
self.configured = False
self._config_source = config_source
self._pending_defaults = deque()
self.finalized = False
self._finalize_mutex = threading.Lock()
self._pending = deque()
self._tasks = tasks
if not isinstance(self._tasks, TaskRegistry):
self._tasks = TaskRegistry(self._tasks or {})
# If the class defines a custom __reduce_args__ we need to use
# the old way of pickling apps, which is pickling a list of
# args instead of the new way that pickles a dict of keywords.
self._using_v1_reduce = app_has_custom(self, '__reduce_args__')
# these options are moved to the config to
# simplify pickling of the app object.
self._preconf = changes or {}
if broker:
self._preconf['BROKER_URL'] = broker
if backend:
self._preconf['CELERY_RESULT_BACKEND'] = backend
if include:
self._preconf['CELERY_IMPORTS'] = include
# - Apply fixups.
self.fixups = set(self.builtin_fixups) if fixups is None else fixups
# ...store fixup instances in _fixups to keep weakrefs alive.
self._fixups = [symbol_by_name(fixup)(self) for fixup in self.fixups]
if self.set_as_current:
self.set_current()
self.on_init()
_register_app(self)
def set_current(self):
_set_current_app(self)
def set_default(self):
set_default_app(self)
def __enter__(self):
return self
def __exit__(self, *exc_info):
self.close()
def close(self):
self._maybe_close_pool()
def on_init(self):
"""Optional callback called at init."""
pass
def start(self, argv=None):
return instantiate(
'celery.bin.celery:CeleryCommand',
app=self).execute_from_commandline(argv)
def worker_main(self, argv=None):
return instantiate(
'celery.bin.worker:worker',
app=self).execute_from_commandline(argv)
def task(self, *args, **opts):
"""Creates new task class from any callable."""
if _EXECV and not opts.get('_force_evaluate'):
# When using execv the task in the original module will point to a
# different app, so doing things like 'add.request' will point to
# a differnt task instance. This makes sure it will always use
# the task instance from the current app.
# Really need a better solution for this :(
from . import shared_task
return shared_task(*args, _force_evaluate=True, **opts)
def inner_create_task_cls(shared=True, filter=None, **opts):
_filt = filter # stupid 2to3
def _create_task_cls(fun):
if shared:
def cons(app):
return app._task_from_fun(fun, **opts)
cons.__name__ = fun.__name__
connect_on_app_finalize(cons)
if self.accept_magic_kwargs: # compat mode
task = self._task_from_fun(fun, **opts)
if filter:
task = filter(task)
return task
if self.finalized or opts.get('_force_evaluate'):
ret = self._task_from_fun(fun, **opts)
else:
# return a proxy object that evaluates on first use
ret = PromiseProxy(self._task_from_fun, (fun, ), opts,
__doc__=fun.__doc__)
self._pending.append(ret)
if _filt:
return _filt(ret)
return ret
return _create_task_cls
if len(args) == 1:
if callable(args[0]):
return inner_create_task_cls(**opts)(*args)
raise TypeError('argument 1 to @task() must be a callable')
if args:
raise TypeError(
'@task() takes exactly 1 argument ({0} given)'.format(
sum([len(args), len(opts)])))
return inner_create_task_cls(**opts)
def _task_from_fun(self, fun, **options):
if not self.finalized and not self.autofinalize:
raise RuntimeError('Contract breach: app not finalized')
base = options.pop('base', None) or self.Task
bind = options.pop('bind', False)
T = type(fun.__name__, (base, ), dict({
'app': self,
'accept_magic_kwargs': False,
'run': fun if bind else staticmethod(fun),
'_decorated': True,
'__doc__': fun.__doc__,
'__module__': fun.__module__,
'__wrapped__': fun}, **options))()
task = self._tasks[T.name] # return global instance.
return task
def finalize(self, auto=False):
with self._finalize_mutex:
if not self.finalized:
if auto and not self.autofinalize:
raise RuntimeError('Contract breach: app not finalized')
self.finalized = True
_announce_app_finalized(self)
pending = self._pending
while pending:
maybe_evaluate(pending.popleft())
for task in values(self._tasks):
task.bind(self)
def add_defaults(self, fun):
if not callable(fun):
d, fun = fun, lambda: d
if self.configured:
return self.conf.add_defaults(fun())
self._pending_defaults.append(fun)
def config_from_object(self, obj, silent=False, force=False):
self._config_source = obj
if force or self.configured:
del(self.conf)
return self.loader.config_from_object(obj, silent=silent)
def config_from_envvar(self, variable_name, silent=False, force=False):
module_name = os.environ.get(variable_name)
if not module_name:
if silent:
return False
raise ImproperlyConfigured(
ERR_ENVVAR_NOT_SET.format(variable_name))
return self.config_from_object(module_name, silent=silent, force=force)
def config_from_cmdline(self, argv, namespace='celery'):
self.conf.update(self.loader.cmdline_config_parser(argv, namespace))
def setup_security(self, allowed_serializers=None, key=None, cert=None,
store=None, digest='sha1', serializer='json'):
from celery.security import setup_security
return setup_security(allowed_serializers, key, cert,
store, digest, serializer, app=self)
def autodiscover_tasks(self, packages, related_name='tasks', force=False):
if force:
return self._autodiscover_tasks(packages, related_name)
signals.import_modules.connect(promise(
self._autodiscover_tasks, (packages, related_name),
), weak=False, sender=self)
def _autodiscover_tasks(self, packages, related_name='tasks', **kwargs):
# argument may be lazy
packages = packages() if callable(packages) else packages
self.loader.autodiscover_tasks(packages, related_name)
def send_task(self, name, args=None, kwargs=None, countdown=None,
eta=None, task_id=None, producer=None, connection=None,
router=None, result_cls=None, expires=None,
publisher=None, link=None, link_error=None,
add_to_parent=True, reply_to=None, **options):
task_id = task_id or uuid()
producer = producer or publisher # XXX compat
router = router or self.amqp.router
conf = self.conf
if conf.CELERY_ALWAYS_EAGER: # pragma: no cover
warnings.warn(AlwaysEagerIgnored(
'CELERY_ALWAYS_EAGER has no effect on send_task',
), stacklevel=2)
options = router.route(options, name, args, kwargs)
if connection:
producer = self.amqp.TaskProducer(connection)
with self.producer_or_acquire(producer) as P:
self.backend.on_task_call(P, task_id)
task_id = P.publish_task(
name, args, kwargs, countdown=countdown, eta=eta,
task_id=task_id, expires=expires,
callbacks=maybe_list(link), errbacks=maybe_list(link_error),
reply_to=reply_to or self.oid, **options
)
result = (result_cls or self.AsyncResult)(task_id)
if add_to_parent:
parent = get_current_worker_task()
if parent:
parent.add_trail(result)
return result
def connection(self, hostname=None, userid=None, password=None,
virtual_host=None, port=None, ssl=None,
connect_timeout=None, transport=None,
transport_options=None, heartbeat=None,
login_method=None, failover_strategy=None, **kwargs):
conf = self.conf
return self.amqp.Connection(
hostname or conf.BROKER_URL,
userid or conf.BROKER_USER,
password or conf.BROKER_PASSWORD,
virtual_host or conf.BROKER_VHOST,
port or conf.BROKER_PORT,
transport=transport or conf.BROKER_TRANSPORT,
ssl=self.either('BROKER_USE_SSL', ssl),
heartbeat=heartbeat,
login_method=login_method or conf.BROKER_LOGIN_METHOD,
failover_strategy=(
failover_strategy or conf.BROKER_FAILOVER_STRATEGY
),
transport_options=dict(
conf.BROKER_TRANSPORT_OPTIONS, **transport_options or {}
),
connect_timeout=self.either(
'BROKER_CONNECTION_TIMEOUT', connect_timeout
),
)
broker_connection = connection
def _acquire_connection(self, pool=True):
"""Helper for :meth:`connection_or_acquire`."""
if pool:
return self.pool.acquire(block=True)
return self.connection()
def connection_or_acquire(self, connection=None, pool=True, *_, **__):
return FallbackContext(connection, self._acquire_connection, pool=pool)
default_connection = connection_or_acquire # XXX compat
def producer_or_acquire(self, producer=None):
return FallbackContext(
producer, self.amqp.producer_pool.acquire, block=True,
)
default_producer = producer_or_acquire # XXX compat
def prepare_config(self, c):
"""Prepare configuration before it is merged with the defaults."""
return find_deprecated_settings(c)
def now(self):
return self.loader.now(utc=self.conf.CELERY_ENABLE_UTC)
def mail_admins(self, subject, body, fail_silently=False):
if self.conf.ADMINS:
to = [admin_email for _, admin_email in self.conf.ADMINS]
return self.loader.mail_admins(
subject, body, fail_silently, to=to,
sender=self.conf.SERVER_EMAIL,
host=self.conf.EMAIL_HOST,
port=self.conf.EMAIL_PORT,
user=self.conf.EMAIL_HOST_USER,
password=self.conf.EMAIL_HOST_PASSWORD,
timeout=self.conf.EMAIL_TIMEOUT,
use_ssl=self.conf.EMAIL_USE_SSL,
use_tls=self.conf.EMAIL_USE_TLS,
)
def select_queues(self, queues=None):
return self.amqp.queues.select(queues)
def either(self, default_key, *values):
"""Fallback to the value of a configuration key if none of the
`*values` are true."""
return first(None, values) or self.conf.get(default_key)
def bugreport(self):
return bugreport(self)
def _get_backend(self):
from celery.backends import get_backend_by_url
backend, url = get_backend_by_url(
self.backend_cls or self.conf.CELERY_RESULT_BACKEND,
self.loader)
return backend(app=self, url=url)
def on_configure(self):
"""Callback calld when the app loads configuration"""
pass
def _get_config(self):
self.on_configure()
if self._config_source:
self.loader.config_from_object(self._config_source)
self.configured = True
s = Settings({}, [self.prepare_config(self.loader.conf),
deepcopy(DEFAULTS)])
# load lazy config dict initializers.
pending = self._pending_defaults
while pending:
s.add_defaults(maybe_evaluate(pending.popleft()()))
# preconf options must be explicitly set in the conf, and not
# as defaults or they will not be pickled with the app instance.
# This will cause errors when `CELERYD_FORCE_EXECV=True` as
# the workers will not have a BROKER_URL, CELERY_RESULT_BACKEND,
# or CELERY_IMPORTS set in the config.
if self._preconf:
s.update(self._preconf)
return s
def _after_fork(self, obj_):
self._maybe_close_pool()
def _maybe_close_pool(self):
pool, self._pool = self._pool, None
if pool is not None:
pool.force_close_all()
amqp = self.__dict__.get('amqp')
if amqp is not None:
producer_pool, amqp._producer_pool = amqp._producer_pool, None
if producer_pool is not None:
producer_pool.force_close_all()
def signature(self, *args, **kwargs):
kwargs['app'] = self
return self.canvas.signature(*args, **kwargs)
def create_task_cls(self):
"""Creates a base task class using default configuration
taken from this app."""
return self.subclass_with_self(
self.task_cls, name='Task', attribute='_app',
keep_reduce=True, abstract=True,
)
def subclass_with_self(self, Class, name=None, attribute='app',
reverse=None, keep_reduce=False, **kw):
"""Subclass an app-compatible class by setting its app attribute
to be this app instance.
App-compatible means that the class has a class attribute that
provides the default app it should use, e.g.
``class Foo: app = None``.
:param Class: The app-compatible class to subclass.
:keyword name: Custom name for the target class.
:keyword attribute: Name of the attribute holding the app,
default is 'app'.
"""
Class = symbol_by_name(Class)
reverse = reverse if reverse else Class.__name__
def __reduce__(self):
return _unpickle_appattr, (reverse, self.__reduce_args__())
attrs = dict({attribute: self}, __module__=Class.__module__,
__doc__=Class.__doc__, **kw)
if not keep_reduce:
attrs['__reduce__'] = __reduce__
return type(name or Class.__name__, (Class, ), attrs)
def _rgetattr(self, path):
return attrgetter(path)(self)
def __repr__(self):
return '<{0} {1}>'.format(type(self).__name__, appstr(self))
def __reduce__(self):
if self._using_v1_reduce:
return self.__reduce_v1__()
return (_unpickle_app_v2, (self.__class__, self.__reduce_keys__()))
def __reduce_v1__(self):
# Reduce only pickles the configuration changes,
# so the default configuration doesn't have to be passed
# between processes.
return (
_unpickle_app,
(self.__class__, self.Pickler) + self.__reduce_args__(),
)
def __reduce_keys__(self):
"""Return keyword arguments used to reconstruct the object
when unpickling."""
return {
'main': self.main,
'changes': self.conf.changes if self.configured else self._preconf,
'loader': self.loader_cls,
'backend': self.backend_cls,
'amqp': self.amqp_cls,
'events': self.events_cls,
'log': self.log_cls,
'control': self.control_cls,
'accept_magic_kwargs': self.accept_magic_kwargs,
'fixups': self.fixups,
'config_source': self._config_source,
'task_cls': self.task_cls,
}
def __reduce_args__(self):
"""Deprecated method, please use :meth:`__reduce_keys__` instead."""
return (self.main, self.conf.changes,
self.loader_cls, self.backend_cls, self.amqp_cls,
self.events_cls, self.log_cls, self.control_cls,
self.accept_magic_kwargs, self._config_source)
@cached_property
def Worker(self):
return self.subclass_with_self('celery.apps.worker:Worker')
@cached_property
def WorkController(self, **kwargs):
return self.subclass_with_self('celery.worker:WorkController')
@cached_property
def Beat(self, **kwargs):
return self.subclass_with_self('celery.apps.beat:Beat')
@cached_property
def Task(self):
return self.create_task_cls()
@cached_property
def annotations(self):
return prepare_annotations(self.conf.CELERY_ANNOTATIONS)
@cached_property
def AsyncResult(self):
return self.subclass_with_self('celery.result:AsyncResult')
@cached_property
def ResultSet(self):
return self.subclass_with_self('celery.result:ResultSet')
@cached_property
def GroupResult(self):
return self.subclass_with_self('celery.result:GroupResult')
@cached_property
def TaskSet(self): # XXX compat
"""Deprecated! Please use :class:`celery.group` instead."""
return self.subclass_with_self('celery.task.sets:TaskSet')
@cached_property
def TaskSetResult(self): # XXX compat
"""Deprecated! Please use :attr:`GroupResult` instead."""
return self.subclass_with_self('celery.result:TaskSetResult')
@property
def pool(self):
if self._pool is None:
_ensure_after_fork()
limit = self.conf.BROKER_POOL_LIMIT
self._pool = self.connection().Pool(limit=limit)
return self._pool
@property
def current_task(self):
return _task_stack.top
@cached_property
def oid(self):
return oid_from(self)
@cached_property
def amqp(self):
return instantiate(self.amqp_cls, app=self)
@cached_property
def backend(self):
return self._get_backend()
@cached_property
def conf(self):
return self._get_config()
@cached_property
def control(self):
return instantiate(self.control_cls, app=self)
@cached_property
def events(self):
return instantiate(self.events_cls, app=self)
@cached_property
def loader(self):
return get_loader_cls(self.loader_cls)(app=self)
@cached_property
def log(self):
return instantiate(self.log_cls, app=self)
@cached_property
def canvas(self):
from celery import canvas
return canvas
@cached_property
def tasks(self):
self.finalize(auto=True)
return self._tasks
@cached_property
def timezone(self):
from celery.utils.timeutils import timezone
conf = self.conf
tz = conf.CELERY_TIMEZONE
if not tz:
return (timezone.get_timezone('UTC') if conf.CELERY_ENABLE_UTC
else timezone.local)
return timezone.get_timezone(self.conf.CELERY_TIMEZONE)
App = Celery # compat
@@ -1,379 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.builtins
~~~~~~~~~~~~~~~~~~~
Built-in tasks that are always available in all
app instances. E.g. chord, group and xmap.
"""
from __future__ import absolute_import
from collections import deque
from celery._state import get_current_worker_task, connect_on_app_finalize
from celery.utils import uuid
from celery.utils.log import get_logger
__all__ = []
logger = get_logger(__name__)
@connect_on_app_finalize
def add_backend_cleanup_task(app):
"""The backend cleanup task can be used to clean up the default result
backend.
If the configured backend requires periodic cleanup this task is also
automatically configured to run every day at 4am (requires
:program:`celery beat` to be running).
"""
@app.task(name='celery.backend_cleanup',
shared=False, _force_evaluate=True)
def backend_cleanup():
app.backend.cleanup()
return backend_cleanup
@connect_on_app_finalize
def add_unlock_chord_task(app):
"""This task is used by result backends without native chord support.
It joins chords by creating a task chain polling the header for completion.
"""
from celery.canvas import signature
from celery.exceptions import ChordError
from celery.result import allow_join_result, result_from_tuple
default_propagate = app.conf.CELERY_CHORD_PROPAGATES
@app.task(name='celery.chord_unlock', max_retries=None, shared=False,
default_retry_delay=1, ignore_result=True, _force_evaluate=True,
bind=True)
def unlock_chord(self, group_id, callback, interval=None, propagate=None,
max_retries=None, result=None,
Result=app.AsyncResult, GroupResult=app.GroupResult,
result_from_tuple=result_from_tuple):
# if propagate is disabled exceptions raised by chord tasks
# will be sent as part of the result list to the chord callback.
# Since 3.1 propagate will be enabled by default, and instead
# the chord callback changes state to FAILURE with the
# exception set to ChordError.
propagate = default_propagate if propagate is None else propagate
if interval is None:
interval = self.default_retry_delay
# check if the task group is ready, and if so apply the callback.
deps = GroupResult(
group_id,
[result_from_tuple(r, app=app) for r in result],
app=app,
)
j = deps.join_native if deps.supports_native_join else deps.join
try:
ready = deps.ready()
except Exception as exc:
raise self.retry(
exc=exc, countdown=interval, max_retries=max_retries,
)
else:
if not ready:
raise self.retry(countdown=interval, max_retries=max_retries)
callback = signature(callback, app=app)
try:
with allow_join_result():
ret = j(timeout=3.0, propagate=propagate)
except Exception as exc:
try:
culprit = next(deps._failed_join_report())
reason = 'Dependency {0.id} raised {1!r}'.format(
culprit, exc,
)
except StopIteration:
reason = repr(exc)
logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
app.backend.chord_error_from_stack(callback,
ChordError(reason))
else:
try:
callback.delay(ret)
except Exception as exc:
logger.error('Chord %r raised: %r', group_id, exc, exc_info=1)
app.backend.chord_error_from_stack(
callback,
exc=ChordError('Callback error: {0!r}'.format(exc)),
)
return unlock_chord
@connect_on_app_finalize
def add_map_task(app):
from celery.canvas import signature
@app.task(name='celery.map', shared=False, _force_evaluate=True)
def xmap(task, it):
task = signature(task, app=app).type
return [task(item) for item in it]
return xmap
@connect_on_app_finalize
def add_starmap_task(app):
from celery.canvas import signature
@app.task(name='celery.starmap', shared=False, _force_evaluate=True)
def xstarmap(task, it):
task = signature(task, app=app).type
return [task(*item) for item in it]
return xstarmap
@connect_on_app_finalize
def add_chunk_task(app):
from celery.canvas import chunks as _chunks
@app.task(name='celery.chunks', shared=False, _force_evaluate=True)
def chunks(task, it, n):
return _chunks.apply_chunks(task, it, n)
return chunks
@connect_on_app_finalize
def add_group_task(app):
_app = app
from celery.canvas import maybe_signature, signature
from celery.result import result_from_tuple
class Group(app.Task):
app = _app
name = 'celery.group'
accept_magic_kwargs = False
_decorated = True
def run(self, tasks, result, group_id, partial_args,
add_to_parent=True):
app = self.app
result = result_from_tuple(result, app)
# any partial args are added to all tasks in the group
taskit = (signature(task, app=app).clone(partial_args)
for i, task in enumerate(tasks))
if self.request.is_eager or app.conf.CELERY_ALWAYS_EAGER:
return app.GroupResult(
result.id,
[stask.apply(group_id=group_id) for stask in taskit],
)
with app.producer_or_acquire() as pub:
[stask.apply_async(group_id=group_id, producer=pub,
add_to_parent=False) for stask in taskit]
parent = get_current_worker_task()
if add_to_parent and parent:
parent.add_trail(result)
return result
def prepare(self, options, tasks, args, **kwargs):
options['group_id'] = group_id = (
options.setdefault('task_id', uuid()))
def prepare_member(task):
task = maybe_signature(task, app=self.app)
task.options['group_id'] = group_id
return task, task.freeze()
try:
tasks, res = list(zip(
*[prepare_member(task) for task in tasks]
))
except ValueError: # tasks empty
tasks, res = [], []
return (tasks, self.app.GroupResult(group_id, res), group_id, args)
def apply_async(self, partial_args=(), kwargs={}, **options):
if self.app.conf.CELERY_ALWAYS_EAGER:
return self.apply(partial_args, kwargs, **options)
tasks, result, gid, args = self.prepare(
options, args=partial_args, **kwargs
)
super(Group, self).apply_async((
list(tasks), result.as_tuple(), gid, args), **options
)
return result
def apply(self, args=(), kwargs={}, **options):
return super(Group, self).apply(
self.prepare(options, args=args, **kwargs),
**options).get()
return Group
@connect_on_app_finalize
def add_chain_task(app):
from celery.canvas import (
Signature, chain, chord, group, maybe_signature, maybe_unroll_group,
)
_app = app
class Chain(app.Task):
app = _app
name = 'celery.chain'
accept_magic_kwargs = False
_decorated = True
def prepare_steps(self, args, tasks):
app = self.app
steps = deque(tasks)
next_step = prev_task = prev_res = None
tasks, results = [], []
i = 0
while steps:
# First task get partial args from chain.
task = maybe_signature(steps.popleft(), app=app)
task = task.clone() if i else task.clone(args)
res = task.freeze()
i += 1
if isinstance(task, group):
task = maybe_unroll_group(task)
if isinstance(task, chain):
# splice the chain
steps.extendleft(reversed(task.tasks))
continue
elif isinstance(task, group) and steps and \
not isinstance(steps[0], group):
# automatically upgrade group(..) | s to chord(group, s)
try:
next_step = steps.popleft()
# for chords we freeze by pretending it's a normal
# task instead of a group.
res = Signature.freeze(next_step)
task = chord(task, body=next_step, task_id=res.task_id)
except IndexError:
pass # no callback, so keep as group
if prev_task:
# link previous task to this task.
prev_task.link(task)
# set the results parent attribute.
if not res.parent:
res.parent = prev_res
if not isinstance(prev_task, chord):
results.append(res)
tasks.append(task)
prev_task, prev_res = task, res
return tasks, results
def apply_async(self, args=(), kwargs={}, group_id=None, chord=None,
task_id=None, link=None, link_error=None, **options):
if self.app.conf.CELERY_ALWAYS_EAGER:
return self.apply(args, kwargs, **options)
options.pop('publisher', None)
tasks, results = self.prepare_steps(args, kwargs['tasks'])
result = results[-1]
if group_id:
tasks[-1].set(group_id=group_id)
if chord:
tasks[-1].set(chord=chord)
if task_id:
tasks[-1].set(task_id=task_id)
result = tasks[-1].type.AsyncResult(task_id)
# make sure we can do a link() and link_error() on a chain object.
if link:
tasks[-1].set(link=link)
# and if any task in the chain fails, call the errbacks
if link_error:
for task in tasks:
task.set(link_error=link_error)
tasks[0].apply_async(**options)
return result
def apply(self, args=(), kwargs={}, signature=maybe_signature,
**options):
app = self.app
last, fargs = None, args # fargs passed to first task only
for task in kwargs['tasks']:
res = signature(task, app=app).clone(fargs).apply(
last and (last.get(), ),
)
res.parent, last, fargs = last, res, None
return last
return Chain
@connect_on_app_finalize
def add_chord_task(app):
"""Every chord is executed in a dedicated task, so that the chord
can be used as a signature, and this generates the task
responsible for that."""
from celery import group
from celery.canvas import maybe_signature
_app = app
default_propagate = app.conf.CELERY_CHORD_PROPAGATES
class Chord(app.Task):
app = _app
name = 'celery.chord'
accept_magic_kwargs = False
ignore_result = False
_decorated = True
def run(self, header, body, partial_args=(), interval=None,
countdown=1, max_retries=None, propagate=None,
eager=False, **kwargs):
app = self.app
propagate = default_propagate if propagate is None else propagate
group_id = uuid()
# - convert back to group if serialized
tasks = header.tasks if isinstance(header, group) else header
header = group([
maybe_signature(s, app=app).clone() for s in tasks
], app=self.app)
# - eager applies the group inline
if eager:
return header.apply(args=partial_args, task_id=group_id)
body['chord_size'] = len(header.tasks)
results = header.freeze(group_id=group_id, chord=body).results
return self.backend.apply_chord(
header, partial_args, group_id,
body, interval=interval, countdown=countdown,
max_retries=max_retries, propagate=propagate, result=results,
)
def apply_async(self, args=(), kwargs={}, task_id=None,
group_id=None, chord=None, **options):
app = self.app
if app.conf.CELERY_ALWAYS_EAGER:
return self.apply(args, kwargs, **options)
header = kwargs.pop('header')
body = kwargs.pop('body')
header, body = (maybe_signature(header, app=app),
maybe_signature(body, app=app))
# forward certain options to body
if chord is not None:
body.options['chord'] = chord
if group_id is not None:
body.options['group_id'] = group_id
[body.link(s) for s in options.pop('link', [])]
[body.link_error(s) for s in options.pop('link_error', [])]
body_result = body.freeze(task_id)
parent = super(Chord, self).apply_async((header, body, args),
kwargs, **options)
body_result.parent = parent
return body_result
def apply(self, args=(), kwargs={}, propagate=True, **options):
body = kwargs['body']
res = super(Chord, self).apply(args, dict(kwargs, eager=True),
**options)
return maybe_signature(body, app=self.app).apply(
args=(res.get(propagate=propagate).get(), ))
return Chord
@@ -1,317 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.control
~~~~~~~~~~~~~~~~~~~
Client for worker remote control commands.
Server implementation is in :mod:`celery.worker.control`.
"""
from __future__ import absolute_import
import warnings
from kombu.pidbox import Mailbox
from kombu.utils import cached_property
from celery.exceptions import DuplicateNodenameWarning
from celery.utils.text import pluralize
__all__ = ['Inspect', 'Control', 'flatten_reply']
W_DUPNODE = """\
Received multiple replies from node {0}: {1}.
Please make sure you give each node a unique nodename using the `-n` option.\
"""
def flatten_reply(reply):
nodes, dupes = {}, set()
for item in reply:
[dupes.add(name) for name in item if name in nodes]
nodes.update(item)
if dupes:
warnings.warn(DuplicateNodenameWarning(
W_DUPNODE.format(
pluralize(len(dupes), 'name'), ', '.join(sorted(dupes)),
),
))
return nodes
class Inspect(object):
app = None
def __init__(self, destination=None, timeout=1, callback=None,
connection=None, app=None, limit=None):
self.app = app or self.app
self.destination = destination
self.timeout = timeout
self.callback = callback
self.connection = connection
self.limit = limit
def _prepare(self, reply):
if not reply:
return
by_node = flatten_reply(reply)
if self.destination and \
not isinstance(self.destination, (list, tuple)):
return by_node.get(self.destination)
return by_node
def _request(self, command, **kwargs):
return self._prepare(self.app.control.broadcast(
command,
arguments=kwargs,
destination=self.destination,
callback=self.callback,
connection=self.connection,
limit=self.limit,
timeout=self.timeout, reply=True,
))
def report(self):
return self._request('report')
def clock(self):
return self._request('clock')
def active(self, safe=False):
return self._request('dump_active', safe=safe)
def scheduled(self, safe=False):
return self._request('dump_schedule', safe=safe)
def reserved(self, safe=False):
return self._request('dump_reserved', safe=safe)
def stats(self):
return self._request('stats')
def revoked(self):
return self._request('dump_revoked')
def registered(self, *taskinfoitems):
return self._request('dump_tasks', taskinfoitems=taskinfoitems)
registered_tasks = registered
def ping(self):
return self._request('ping')
def active_queues(self):
return self._request('active_queues')
def query_task(self, ids):
return self._request('query_task', ids=ids)
def conf(self, with_defaults=False):
return self._request('dump_conf', with_defaults=with_defaults)
def hello(self, from_node, revoked=None):
return self._request('hello', from_node=from_node, revoked=revoked)
def memsample(self):
return self._request('memsample')
def memdump(self, samples=10):
return self._request('memdump', samples=samples)
def objgraph(self, type='Request', n=200, max_depth=10):
return self._request('objgraph', num=n, max_depth=max_depth, type=type)
class Control(object):
Mailbox = Mailbox
def __init__(self, app=None):
self.app = app
self.mailbox = self.Mailbox('celery', type='fanout', accept=['json'])
@cached_property
def inspect(self):
return self.app.subclass_with_self(Inspect, reverse='control.inspect')
def purge(self, connection=None):
"""Discard all waiting tasks.
This will ignore all tasks waiting for execution, and they will
be deleted from the messaging server.
:returns: the number of tasks discarded.
"""
with self.app.connection_or_acquire(connection) as conn:
return self.app.amqp.TaskConsumer(conn).purge()
discard_all = purge
def election(self, id, topic, action=None, connection=None):
self.broadcast('election', connection=connection, arguments={
'id': id, 'topic': topic, 'action': action,
})
def revoke(self, task_id, destination=None, terminate=False,
signal='SIGTERM', **kwargs):
"""Tell all (or specific) workers to revoke a task by id.
If a task is revoked, the workers will ignore the task and
not execute it after all.
:param task_id: Id of the task to revoke.
:keyword terminate: Also terminate the process currently working
on the task (if any).
:keyword signal: Name of signal to send to process if terminate.
Default is TERM.
See :meth:`broadcast` for supported keyword arguments.
"""
return self.broadcast('revoke', destination=destination,
arguments={'task_id': task_id,
'terminate': terminate,
'signal': signal}, **kwargs)
def ping(self, destination=None, timeout=1, **kwargs):
"""Ping all (or specific) workers.
Will return the list of answers.
See :meth:`broadcast` for supported keyword arguments.
"""
return self.broadcast('ping', reply=True, destination=destination,
timeout=timeout, **kwargs)
def rate_limit(self, task_name, rate_limit, destination=None, **kwargs):
"""Tell all (or specific) workers to set a new rate limit
for task by type.
:param task_name: Name of task to change rate limit for.
:param rate_limit: The rate limit as tasks per second, or a rate limit
string (`'100/m'`, etc.
see :attr:`celery.task.base.Task.rate_limit` for
more information).
See :meth:`broadcast` for supported keyword arguments.
"""
return self.broadcast('rate_limit', destination=destination,
arguments={'task_name': task_name,
'rate_limit': rate_limit},
**kwargs)
def add_consumer(self, queue, exchange=None, exchange_type='direct',
routing_key=None, options=None, **kwargs):
"""Tell all (or specific) workers to start consuming from a new queue.
Only the queue name is required as if only the queue is specified
then the exchange/routing key will be set to the same name (
like automatic queues do).
.. note::
This command does not respect the default queue/exchange
options in the configuration.
:param queue: Name of queue to start consuming from.
:keyword exchange: Optional name of exchange.
:keyword exchange_type: Type of exchange (defaults to 'direct')
command to, when empty broadcast to all workers.
:keyword routing_key: Optional routing key.
:keyword options: Additional options as supported
by :meth:`kombu.entitiy.Queue.from_dict`.
See :meth:`broadcast` for supported keyword arguments.
"""
return self.broadcast(
'add_consumer',
arguments=dict({'queue': queue, 'exchange': exchange,
'exchange_type': exchange_type,
'routing_key': routing_key}, **options or {}),
**kwargs
)
def cancel_consumer(self, queue, **kwargs):
"""Tell all (or specific) workers to stop consuming from ``queue``.
Supports the same keyword arguments as :meth:`broadcast`.
"""
return self.broadcast(
'cancel_consumer', arguments={'queue': queue}, **kwargs
)
def time_limit(self, task_name, soft=None, hard=None, **kwargs):
"""Tell all (or specific) workers to set time limits for
a task by type.
:param task_name: Name of task to change time limits for.
:keyword soft: New soft time limit (in seconds).
:keyword hard: New hard time limit (in seconds).
Any additional keyword arguments are passed on to :meth:`broadcast`.
"""
return self.broadcast(
'time_limit',
arguments={'task_name': task_name,
'hard': hard, 'soft': soft}, **kwargs)
def enable_events(self, destination=None, **kwargs):
"""Tell all (or specific) workers to enable events."""
return self.broadcast('enable_events', {}, destination, **kwargs)
def disable_events(self, destination=None, **kwargs):
"""Tell all (or specific) workers to disable events."""
return self.broadcast('disable_events', {}, destination, **kwargs)
def pool_grow(self, n=1, destination=None, **kwargs):
"""Tell all (or specific) workers to grow the pool by ``n``.
Supports the same arguments as :meth:`broadcast`.
"""
return self.broadcast('pool_grow', {'n': n}, destination, **kwargs)
def pool_shrink(self, n=1, destination=None, **kwargs):
"""Tell all (or specific) workers to shrink the pool by ``n``.
Supports the same arguments as :meth:`broadcast`.
"""
return self.broadcast('pool_shrink', {'n': n}, destination, **kwargs)
def autoscale(self, max, min, destination=None, **kwargs):
"""Change worker(s) autoscale setting.
Supports the same arguments as :meth:`broadcast`.
"""
return self.broadcast(
'autoscale', {'max': max, 'min': min}, destination, **kwargs)
def broadcast(self, command, arguments=None, destination=None,
connection=None, reply=False, timeout=1, limit=None,
callback=None, channel=None, **extra_kwargs):
"""Broadcast a control command to the celery workers.
:param command: Name of command to send.
:param arguments: Keyword arguments for the command.
:keyword destination: If set, a list of the hosts to send the
command to, when empty broadcast to all workers.
:keyword connection: Custom broker connection to use, if not set,
a connection will be established automatically.
:keyword reply: Wait for and return the reply.
:keyword timeout: Timeout in seconds to wait for the reply.
:keyword limit: Limit number of replies.
:keyword callback: Callback called immediately for each reply
received.
"""
with self.app.connection_or_acquire(connection) as conn:
arguments = dict(arguments or {}, **extra_kwargs)
return self.mailbox(conn)._broadcast(
command, arguments, destination, reply, timeout,
limit, callback, channel=channel,
)
@@ -1,274 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.defaults
~~~~~~~~~~~~~~~~~~~
Configuration introspection and defaults.
"""
from __future__ import absolute_import
import sys
from collections import deque, namedtuple
from datetime import timedelta
from celery.five import items
from celery.utils import strtobool
from celery.utils.functional import memoize
__all__ = ['Option', 'NAMESPACES', 'flatten', 'find']
is_jython = sys.platform.startswith('java')
is_pypy = hasattr(sys, 'pypy_version_info')
DEFAULT_POOL = 'prefork'
if is_jython:
DEFAULT_POOL = 'threads'
elif is_pypy:
if sys.pypy_version_info[0:3] < (1, 5, 0):
DEFAULT_POOL = 'solo'
else:
DEFAULT_POOL = 'prefork'
DEFAULT_ACCEPT_CONTENT = ['json', 'pickle', 'msgpack', 'yaml']
DEFAULT_PROCESS_LOG_FMT = """
[%(asctime)s: %(levelname)s/%(processName)s] %(message)s
""".strip()
DEFAULT_LOG_FMT = '[%(asctime)s: %(levelname)s] %(message)s'
DEFAULT_TASK_LOG_FMT = """[%(asctime)s: %(levelname)s/%(processName)s] \
%(task_name)s[%(task_id)s]: %(message)s"""
_BROKER_OLD = {'deprecate_by': '2.5', 'remove_by': '4.0',
'alt': 'BROKER_URL setting'}
_REDIS_OLD = {'deprecate_by': '2.5', 'remove_by': '4.0',
'alt': 'URL form of CELERY_RESULT_BACKEND'}
searchresult = namedtuple('searchresult', ('namespace', 'key', 'type'))
# logging: processName first introduced in Py 2.6.2 (Issue #1644).
if sys.version_info < (2, 6, 2):
DEFAULT_PROCESS_LOG_FMT = DEFAULT_LOG_FMT
class Option(object):
alt = None
deprecate_by = None
remove_by = None
typemap = dict(string=str, int=int, float=float, any=lambda v: v,
bool=strtobool, dict=dict, tuple=tuple)
def __init__(self, default=None, *args, **kwargs):
self.default = default
self.type = kwargs.get('type') or 'string'
for attr, value in items(kwargs):
setattr(self, attr, value)
def to_python(self, value):
return self.typemap[self.type](value)
def __repr__(self):
return '<Option: type->{0} default->{1!r}>'.format(self.type,
self.default)
NAMESPACES = {
'BROKER': {
'URL': Option(None, type='string'),
'CONNECTION_TIMEOUT': Option(4, type='float'),
'CONNECTION_RETRY': Option(True, type='bool'),
'CONNECTION_MAX_RETRIES': Option(100, type='int'),
'FAILOVER_STRATEGY': Option(None, type='string'),
'HEARTBEAT': Option(None, type='int'),
'HEARTBEAT_CHECKRATE': Option(3.0, type='int'),
'LOGIN_METHOD': Option(None, type='string'),
'POOL_LIMIT': Option(10, type='int'),
'USE_SSL': Option(False, type='bool'),
'TRANSPORT': Option(type='string'),
'TRANSPORT_OPTIONS': Option({}, type='dict'),
'HOST': Option(type='string', **_BROKER_OLD),
'PORT': Option(type='int', **_BROKER_OLD),
'USER': Option(type='string', **_BROKER_OLD),
'PASSWORD': Option(type='string', **_BROKER_OLD),
'VHOST': Option(type='string', **_BROKER_OLD),
},
'CASSANDRA': {
'COLUMN_FAMILY': Option(type='string'),
'DETAILED_MODE': Option(False, type='bool'),
'KEYSPACE': Option(type='string'),
'READ_CONSISTENCY': Option(type='string'),
'SERVERS': Option(type='list'),
'WRITE_CONSISTENCY': Option(type='string'),
},
'CELERY': {
'ACCEPT_CONTENT': Option(DEFAULT_ACCEPT_CONTENT, type='list'),
'ACKS_LATE': Option(False, type='bool'),
'ALWAYS_EAGER': Option(False, type='bool'),
'ANNOTATIONS': Option(type='any'),
'BROADCAST_QUEUE': Option('celeryctl'),
'BROADCAST_EXCHANGE': Option('celeryctl'),
'BROADCAST_EXCHANGE_TYPE': Option('fanout'),
'CACHE_BACKEND': Option(),
'CACHE_BACKEND_OPTIONS': Option({}, type='dict'),
'CHORD_PROPAGATES': Option(True, type='bool'),
'COUCHBASE_BACKEND_SETTINGS': Option(None, type='dict'),
'CREATE_MISSING_QUEUES': Option(True, type='bool'),
'DEFAULT_RATE_LIMIT': Option(type='string'),
'DISABLE_RATE_LIMITS': Option(False, type='bool'),
'DEFAULT_ROUTING_KEY': Option('celery'),
'DEFAULT_QUEUE': Option('celery'),
'DEFAULT_EXCHANGE': Option('celery'),
'DEFAULT_EXCHANGE_TYPE': Option('direct'),
'DEFAULT_DELIVERY_MODE': Option(2, type='string'),
'EAGER_PROPAGATES_EXCEPTIONS': Option(False, type='bool'),
'ENABLE_UTC': Option(True, type='bool'),
'ENABLE_REMOTE_CONTROL': Option(True, type='bool'),
'EVENT_SERIALIZER': Option('json'),
'EVENT_QUEUE_EXPIRES': Option(None, type='float'),
'EVENT_QUEUE_TTL': Option(None, type='float'),
'IMPORTS': Option((), type='tuple'),
'INCLUDE': Option((), type='tuple'),
'IGNORE_RESULT': Option(False, type='bool'),
'MAX_CACHED_RESULTS': Option(100, type='int'),
'MESSAGE_COMPRESSION': Option(type='string'),
'MONGODB_BACKEND_SETTINGS': Option(type='dict'),
'REDIS_HOST': Option(type='string', **_REDIS_OLD),
'REDIS_PORT': Option(type='int', **_REDIS_OLD),
'REDIS_DB': Option(type='int', **_REDIS_OLD),
'REDIS_PASSWORD': Option(type='string', **_REDIS_OLD),
'REDIS_MAX_CONNECTIONS': Option(type='int'),
'RESULT_BACKEND': Option(type='string'),
'RESULT_DB_SHORT_LIVED_SESSIONS': Option(False, type='bool'),
'RESULT_DB_TABLENAMES': Option(type='dict'),
'RESULT_DBURI': Option(),
'RESULT_ENGINE_OPTIONS': Option(type='dict'),
'RESULT_EXCHANGE': Option('celeryresults'),
'RESULT_EXCHANGE_TYPE': Option('direct'),
'RESULT_SERIALIZER': Option('pickle'),
'RESULT_PERSISTENT': Option(None, type='bool'),
'ROUTES': Option(type='any'),
'SEND_EVENTS': Option(False, type='bool'),
'SEND_TASK_ERROR_EMAILS': Option(False, type='bool'),
'SEND_TASK_SENT_EVENT': Option(False, type='bool'),
'STORE_ERRORS_EVEN_IF_IGNORED': Option(False, type='bool'),
'TASK_PUBLISH_RETRY': Option(True, type='bool'),
'TASK_PUBLISH_RETRY_POLICY': Option({
'max_retries': 3,
'interval_start': 0,
'interval_max': 1,
'interval_step': 0.2}, type='dict'),
'TASK_RESULT_EXPIRES': Option(timedelta(days=1), type='float'),
'TASK_SERIALIZER': Option('pickle'),
'TIMEZONE': Option(type='string'),
'TRACK_STARTED': Option(False, type='bool'),
'REDIRECT_STDOUTS': Option(True, type='bool'),
'REDIRECT_STDOUTS_LEVEL': Option('WARNING'),
'QUEUES': Option(type='dict'),
'QUEUE_HA_POLICY': Option(None, type='string'),
'SECURITY_KEY': Option(type='string'),
'SECURITY_CERTIFICATE': Option(type='string'),
'SECURITY_CERT_STORE': Option(type='string'),
'WORKER_DIRECT': Option(False, type='bool'),
},
'CELERYD': {
'AGENT': Option(None, type='string'),
'AUTOSCALER': Option('celery.worker.autoscale:Autoscaler'),
'AUTORELOADER': Option('celery.worker.autoreload:Autoreloader'),
'CONCURRENCY': Option(0, type='int'),
'TIMER': Option(type='string'),
'TIMER_PRECISION': Option(1.0, type='float'),
'FORCE_EXECV': Option(False, type='bool'),
'HIJACK_ROOT_LOGGER': Option(True, type='bool'),
'CONSUMER': Option('celery.worker.consumer:Consumer', type='string'),
'LOG_FORMAT': Option(DEFAULT_PROCESS_LOG_FMT),
'LOG_COLOR': Option(type='bool'),
'LOG_LEVEL': Option('WARN', deprecate_by='2.4', remove_by='4.0',
alt='--loglevel argument'),
'LOG_FILE': Option(deprecate_by='2.4', remove_by='4.0',
alt='--logfile argument'),
'MAX_TASKS_PER_CHILD': Option(type='int'),
'POOL': Option(DEFAULT_POOL),
'POOL_PUTLOCKS': Option(True, type='bool'),
'POOL_RESTARTS': Option(False, type='bool'),
'PREFETCH_MULTIPLIER': Option(4, type='int'),
'STATE_DB': Option(),
'TASK_LOG_FORMAT': Option(DEFAULT_TASK_LOG_FMT),
'TASK_SOFT_TIME_LIMIT': Option(type='float'),
'TASK_TIME_LIMIT': Option(type='float'),
'WORKER_LOST_WAIT': Option(10.0, type='float')
},
'CELERYBEAT': {
'SCHEDULE': Option({}, type='dict'),
'SCHEDULER': Option('celery.beat:PersistentScheduler'),
'SCHEDULE_FILENAME': Option('celerybeat-schedule'),
'SYNC_EVERY': Option(0, type='int'),
'MAX_LOOP_INTERVAL': Option(0, type='float'),
'LOG_LEVEL': Option('INFO', deprecate_by='2.4', remove_by='4.0',
alt='--loglevel argument'),
'LOG_FILE': Option(deprecate_by='2.4', remove_by='4.0',
alt='--logfile argument'),
},
'CELERYMON': {
'LOG_LEVEL': Option('INFO', deprecate_by='2.4', remove_by='4.0',
alt='--loglevel argument'),
'LOG_FILE': Option(deprecate_by='2.4', remove_by='4.0',
alt='--logfile argument'),
'LOG_FORMAT': Option(DEFAULT_LOG_FMT),
},
'EMAIL': {
'HOST': Option('localhost'),
'PORT': Option(25, type='int'),
'HOST_USER': Option(),
'HOST_PASSWORD': Option(),
'TIMEOUT': Option(2, type='float'),
'USE_SSL': Option(False, type='bool'),
'USE_TLS': Option(False, type='bool'),
},
'SERVER_EMAIL': Option('celery@localhost'),
'ADMINS': Option((), type='tuple'),
}
def flatten(d, ns=''):
stack = deque([(ns, d)])
while stack:
name, space = stack.popleft()
for key, value in items(space):
if isinstance(value, dict):
stack.append((name + key + '_', value))
else:
yield name + key, value
DEFAULTS = dict((key, value.default) for key, value in flatten(NAMESPACES))
def find_deprecated_settings(source):
from celery.utils import warn_deprecated
for name, opt in flatten(NAMESPACES):
if (opt.deprecate_by or opt.remove_by) and getattr(source, name, None):
warn_deprecated(description='The {0!r} setting'.format(name),
deprecation=opt.deprecate_by,
removal=opt.remove_by,
alternative='Use the {0.alt} instead'.format(opt))
return source
@memoize(maxsize=None)
def find(name, namespace='celery'):
# - Try specified namespace first.
namespace = namespace.upper()
try:
return searchresult(
namespace, name.upper(), NAMESPACES[namespace][name.upper()],
)
except KeyError:
# - Try all the other namespaces.
for ns, keys in items(NAMESPACES):
if ns.upper() == name.upper():
return searchresult(None, ns, keys)
elif isinstance(keys, dict):
try:
return searchresult(ns, name.upper(), keys[name.upper()])
except KeyError:
pass
# - See if name is a qualname last.
return searchresult(None, name.upper(), DEFAULTS[name.upper()])
@@ -1,257 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.log
~~~~~~~~~~~~~~
The Celery instances logging section: ``Celery.log``.
Sets up logging for the worker and other programs,
redirects stdouts, colors log output, patches logging
related compatibility fixes, and so on.
"""
from __future__ import absolute_import
import logging
import os
import sys
from logging.handlers import WatchedFileHandler
from kombu.log import NullHandler
from kombu.utils.encoding import set_default_encoding_file
from celery import signals
from celery._state import get_current_task
from celery.five import class_property, string_t
from celery.utils import isatty, node_format
from celery.utils.log import (
get_logger, mlevel,
ColorFormatter, ensure_process_aware_logger,
LoggingProxy, get_multiprocessing_logger,
reset_multiprocessing_logger,
)
from celery.utils.term import colored
__all__ = ['TaskFormatter', 'Logging']
MP_LOG = os.environ.get('MP_LOG', False)
class TaskFormatter(ColorFormatter):
def format(self, record):
task = get_current_task()
if task and task.request:
record.__dict__.update(task_id=task.request.id,
task_name=task.name)
else:
record.__dict__.setdefault('task_name', '???')
record.__dict__.setdefault('task_id', '???')
return ColorFormatter.format(self, record)
class Logging(object):
#: The logging subsystem is only configured once per process.
#: setup_logging_subsystem sets this flag, and subsequent calls
#: will do nothing.
_setup = False
def __init__(self, app):
self.app = app
self.loglevel = mlevel(self.app.conf.CELERYD_LOG_LEVEL)
self.format = self.app.conf.CELERYD_LOG_FORMAT
self.task_format = self.app.conf.CELERYD_TASK_LOG_FORMAT
self.colorize = self.app.conf.CELERYD_LOG_COLOR
def setup(self, loglevel=None, logfile=None, redirect_stdouts=False,
redirect_level='WARNING', colorize=None, hostname=None):
handled = self.setup_logging_subsystem(
loglevel, logfile, colorize=colorize, hostname=hostname,
)
if not handled:
if redirect_stdouts:
self.redirect_stdouts(redirect_level)
os.environ.update(
CELERY_LOG_LEVEL=str(loglevel) if loglevel else '',
CELERY_LOG_FILE=str(logfile) if logfile else '',
)
return handled
def redirect_stdouts(self, loglevel=None, name='celery.redirected'):
self.redirect_stdouts_to_logger(
get_logger(name), loglevel=loglevel
)
os.environ.update(
CELERY_LOG_REDIRECT='1',
CELERY_LOG_REDIRECT_LEVEL=str(loglevel or ''),
)
def setup_logging_subsystem(self, loglevel=None, logfile=None, format=None,
colorize=None, hostname=None, **kwargs):
if self.already_setup:
return
if logfile and hostname:
logfile = node_format(logfile, hostname)
self.already_setup = True
loglevel = mlevel(loglevel or self.loglevel)
format = format or self.format
colorize = self.supports_color(colorize, logfile)
reset_multiprocessing_logger()
ensure_process_aware_logger()
receivers = signals.setup_logging.send(
sender=None, loglevel=loglevel, logfile=logfile,
format=format, colorize=colorize,
)
if not receivers:
root = logging.getLogger()
if self.app.conf.CELERYD_HIJACK_ROOT_LOGGER:
root.handlers = []
get_logger('celery').handlers = []
get_logger('celery.task').handlers = []
get_logger('celery.redirected').handlers = []
# Configure root logger
self._configure_logger(
root, logfile, loglevel, format, colorize, **kwargs
)
# Configure the multiprocessing logger
self._configure_logger(
get_multiprocessing_logger(),
logfile, loglevel if MP_LOG else logging.ERROR,
format, colorize, **kwargs
)
signals.after_setup_logger.send(
sender=None, logger=root,
loglevel=loglevel, logfile=logfile,
format=format, colorize=colorize,
)
# then setup the root task logger.
self.setup_task_loggers(loglevel, logfile, colorize=colorize)
try:
stream = logging.getLogger().handlers[0].stream
except (AttributeError, IndexError):
pass
else:
set_default_encoding_file(stream)
# This is a hack for multiprocessing's fork+exec, so that
# logging before Process.run works.
logfile_name = logfile if isinstance(logfile, string_t) else ''
os.environ.update(_MP_FORK_LOGLEVEL_=str(loglevel),
_MP_FORK_LOGFILE_=logfile_name,
_MP_FORK_LOGFORMAT_=format)
return receivers
def _configure_logger(self, logger, logfile, loglevel,
format, colorize, **kwargs):
if logger is not None:
self.setup_handlers(logger, logfile, format,
colorize, **kwargs)
if loglevel:
logger.setLevel(loglevel)
def setup_task_loggers(self, loglevel=None, logfile=None, format=None,
colorize=None, propagate=False, **kwargs):
"""Setup the task logger.
If `logfile` is not specified, then `sys.stderr` is used.
Will return the base task logger object.
"""
loglevel = mlevel(loglevel or self.loglevel)
format = format or self.task_format
colorize = self.supports_color(colorize, logfile)
logger = self.setup_handlers(
get_logger('celery.task'),
logfile, format, colorize,
formatter=TaskFormatter, **kwargs
)
logger.setLevel(loglevel)
# this is an int for some reason, better not question why.
logger.propagate = int(propagate)
signals.after_setup_task_logger.send(
sender=None, logger=logger,
loglevel=loglevel, logfile=logfile,
format=format, colorize=colorize,
)
return logger
def redirect_stdouts_to_logger(self, logger, loglevel=None,
stdout=True, stderr=True):
"""Redirect :class:`sys.stdout` and :class:`sys.stderr` to a
logging instance.
:param logger: The :class:`logging.Logger` instance to redirect to.
:param loglevel: The loglevel redirected messages will be logged as.
"""
proxy = LoggingProxy(logger, loglevel)
if stdout:
sys.stdout = proxy
if stderr:
sys.stderr = proxy
return proxy
def supports_color(self, colorize=None, logfile=None):
colorize = self.colorize if colorize is None else colorize
if self.app.IS_WINDOWS:
# Windows does not support ANSI color codes.
return False
if colorize or colorize is None:
# Only use color if there is no active log file
# and stderr is an actual terminal.
return logfile is None and isatty(sys.stderr)
return colorize
def colored(self, logfile=None, enabled=None):
return colored(enabled=self.supports_color(enabled, logfile))
def setup_handlers(self, logger, logfile, format, colorize,
formatter=ColorFormatter, **kwargs):
if self._is_configured(logger):
return logger
handler = self._detect_handler(logfile)
handler.setFormatter(formatter(format, use_color=colorize))
logger.addHandler(handler)
return logger
def _detect_handler(self, logfile=None):
"""Create log handler with either a filename, an open stream
or :const:`None` (stderr)."""
logfile = sys.__stderr__ if logfile is None else logfile
if hasattr(logfile, 'write'):
return logging.StreamHandler(logfile)
return WatchedFileHandler(logfile)
def _has_handler(self, logger):
if logger.handlers:
return any(not isinstance(h, NullHandler) for h in logger.handlers)
def _is_configured(self, logger):
return self._has_handler(logger) and not getattr(
logger, '_rudimentary_setup', False)
def setup_logger(self, name='celery', *args, **kwargs):
"""Deprecated: No longer used."""
self.setup_logging_subsystem(*args, **kwargs)
return logging.root
def get_default_logger(self, name='celery', **kwargs):
return get_logger(name)
@class_property
def already_setup(cls):
return cls._setup
@already_setup.setter # noqa
def already_setup(cls, was_setup):
cls._setup = was_setup
@@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.registry
~~~~~~~~~~~~~~~~~~~
Registry of available tasks.
"""
from __future__ import absolute_import
import inspect
from importlib import import_module
from celery._state import get_current_app
from celery.exceptions import NotRegistered
from celery.five import items
__all__ = ['TaskRegistry']
class TaskRegistry(dict):
NotRegistered = NotRegistered
def __missing__(self, key):
raise self.NotRegistered(key)
def register(self, task):
"""Register a task in the task registry.
The task will be automatically instantiated if not already an
instance.
"""
self[task.name] = inspect.isclass(task) and task() or task
def unregister(self, name):
"""Unregister task by name.
:param name: name of the task to unregister, or a
:class:`celery.task.base.Task` with a valid `name` attribute.
:raises celery.exceptions.NotRegistered: if the task has not
been registered.
"""
try:
self.pop(getattr(name, 'name', name))
except KeyError:
raise self.NotRegistered(name)
# -- these methods are irrelevant now and will be removed in 4.0
def regular(self):
return self.filter_types('regular')
def periodic(self):
return self.filter_types('periodic')
def filter_types(self, type):
return dict((name, task) for name, task in items(self)
if getattr(task, 'type', 'regular') == type)
def _unpickle_task(name):
return get_current_app().tasks[name]
def _unpickle_task_v2(name, module=None):
if module:
import_module(module)
return get_current_app().tasks[name]
@@ -1,95 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.routes
~~~~~~~~~~~~~
Contains utilities for working with task routers,
(:setting:`CELERY_ROUTES`).
"""
from __future__ import absolute_import
from celery.exceptions import QueueNotFound
from celery.five import string_t
from celery.utils import lpmerge
from celery.utils.functional import firstmethod, mlazy
from celery.utils.imports import instantiate
__all__ = ['MapRoute', 'Router', 'prepare']
_first_route = firstmethod('route_for_task')
class MapRoute(object):
"""Creates a router out of a :class:`dict`."""
def __init__(self, map):
self.map = map
def route_for_task(self, task, *args, **kwargs):
try:
return dict(self.map[task])
except KeyError:
pass
except ValueError:
return {'queue': self.map[task]}
class Router(object):
def __init__(self, routes=None, queues=None,
create_missing=False, app=None):
self.app = app
self.queues = {} if queues is None else queues
self.routes = [] if routes is None else routes
self.create_missing = create_missing
def route(self, options, task, args=(), kwargs={}):
options = self.expand_destination(options) # expands 'queue'
if self.routes:
route = self.lookup_route(task, args, kwargs)
if route: # expands 'queue' in route.
return lpmerge(self.expand_destination(route), options)
if 'queue' not in options:
options = lpmerge(self.expand_destination(
self.app.conf.CELERY_DEFAULT_QUEUE), options)
return options
def expand_destination(self, route):
# Route can be a queue name: convenient for direct exchanges.
if isinstance(route, string_t):
queue, route = route, {}
else:
# can use defaults from configured queue, but override specific
# things (like the routing_key): great for topic exchanges.
queue = route.pop('queue', None)
if queue:
try:
Q = self.queues[queue] # noqa
except KeyError:
raise QueueNotFound(
'Queue {0!r} missing from CELERY_QUEUES'.format(queue))
# needs to be declared by publisher
route['queue'] = Q
return route
def lookup_route(self, task, args=None, kwargs=None):
return _first_route(self.routes, task, args, kwargs)
def prepare(routes):
"""Expands the :setting:`CELERY_ROUTES` setting."""
def expand_route(route):
if isinstance(route, dict):
return MapRoute(route)
if isinstance(route, string_t):
return mlazy(instantiate, route)
return route
if routes is None:
return ()
if not isinstance(routes, (list, tuple)):
routes = (routes, )
return [expand_route(route) for route in routes]
@@ -1,948 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.task
~~~~~~~~~~~~~~~
Task Implementation: Task request context, and the base task class.
"""
from __future__ import absolute_import
import sys
from billiard.einfo import ExceptionInfo
from celery import current_app
from celery import states
from celery._state import _task_stack
from celery.canvas import signature
from celery.exceptions import MaxRetriesExceededError, Reject, Retry
from celery.five import class_property, items, with_metaclass
from celery.local import Proxy
from celery.result import EagerResult
from celery.utils import gen_task_name, fun_takes_kwargs, uuid, maybe_reraise
from celery.utils.functional import mattrgetter, maybe_list
from celery.utils.imports import instantiate
from celery.utils.mail import ErrorMail
from .annotations import resolve_all as resolve_all_annotations
from .registry import _unpickle_task_v2
from .utils import appstr
__all__ = ['Context', 'Task']
#: extracts attributes related to publishing a message from an object.
extract_exec_options = mattrgetter(
'queue', 'routing_key', 'exchange', 'priority', 'expires',
'serializer', 'delivery_mode', 'compression', 'time_limit',
'soft_time_limit', 'immediate', 'mandatory', # imm+man is deprecated
)
# We take __repr__ very seriously around here ;)
R_BOUND_TASK = '<class {0.__name__} of {app}{flags}>'
R_UNBOUND_TASK = '<unbound {0.__name__}{flags}>'
R_SELF_TASK = '<@task {0.name} bound to other {0.__self__}>'
R_INSTANCE = '<@task: {0.name} of {app}{flags}>'
class _CompatShared(object):
def __init__(self, name, cons):
self.name = name
self.cons = cons
def __hash__(self):
return hash(self.name)
def __repr__(self):
return '<OldTask: %r>' % (self.name, )
def __call__(self, app):
return self.cons(app)
def _strflags(flags, default=''):
if flags:
return ' ({0})'.format(', '.join(flags))
return default
def _reprtask(task, fmt=None, flags=None):
flags = list(flags) if flags is not None else []
flags.append('v2 compatible') if task.__v2_compat__ else None
if not fmt:
fmt = R_BOUND_TASK if task._app else R_UNBOUND_TASK
return fmt.format(
task, flags=_strflags(flags),
app=appstr(task._app) if task._app else None,
)
class Context(object):
# Default context
logfile = None
loglevel = None
hostname = None
id = None
args = None
kwargs = None
retries = 0
eta = None
expires = None
is_eager = False
headers = None
delivery_info = None
reply_to = None
correlation_id = None
taskset = None # compat alias to group
group = None
chord = None
utc = None
called_directly = True
callbacks = None
errbacks = None
timelimit = None
_children = None # see property
_protected = 0
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def update(self, *args, **kwargs):
return self.__dict__.update(*args, **kwargs)
def clear(self):
return self.__dict__.clear()
def get(self, key, default=None):
return getattr(self, key, default)
def __repr__(self):
return '<Context: {0!r}>'.format(vars(self))
@property
def children(self):
# children must be an empy list for every thread
if self._children is None:
self._children = []
return self._children
class TaskType(type):
"""Meta class for tasks.
Automatically registers the task in the task registry (except
if the :attr:`Task.abstract`` attribute is set).
If no :attr:`Task.name` attribute is provided, then the name is generated
from the module and class name.
"""
_creation_count = {} # used by old non-abstract task classes
def __new__(cls, name, bases, attrs):
new = super(TaskType, cls).__new__
task_module = attrs.get('__module__') or '__main__'
# - Abstract class: abstract attribute should not be inherited.
abstract = attrs.pop('abstract', None)
if abstract or not attrs.get('autoregister', True):
return new(cls, name, bases, attrs)
# The 'app' attribute is now a property, with the real app located
# in the '_app' attribute. Previously this was a regular attribute,
# so we should support classes defining it.
app = attrs.pop('_app', None) or attrs.pop('app', None)
# Attempt to inherit app from one the bases
if not isinstance(app, Proxy) and app is None:
for base in bases:
if getattr(base, '_app', None):
app = base._app
break
else:
app = current_app._get_current_object()
attrs['_app'] = app
# - Automatically generate missing/empty name.
task_name = attrs.get('name')
if not task_name:
attrs['name'] = task_name = gen_task_name(app, name, task_module)
if not attrs.get('_decorated'):
# non decorated tasks must also be shared in case
# an app is created multiple times due to modules
# imported under multiple names.
# Hairy stuff, here to be compatible with 2.x.
# People should not use non-abstract task classes anymore,
# use the task decorator.
from celery._state import connect_on_app_finalize
unique_name = '.'.join([task_module, name])
if unique_name not in cls._creation_count:
# the creation count is used as a safety
# so that the same task is not added recursively
# to the set of constructors.
cls._creation_count[unique_name] = 1
connect_on_app_finalize(_CompatShared(
unique_name,
lambda app: TaskType.__new__(cls, name, bases,
dict(attrs, _app=app)),
))
# - Create and register class.
# Because of the way import happens (recursively)
# we may or may not be the first time the task tries to register
# with the framework. There should only be one class for each task
# name, so we always return the registered version.
tasks = app._tasks
if task_name not in tasks:
tasks.register(new(cls, name, bases, attrs))
instance = tasks[task_name]
instance.bind(app)
return instance.__class__
def __repr__(cls):
return _reprtask(cls)
@with_metaclass(TaskType)
class Task(object):
"""Task base class.
When called tasks apply the :meth:`run` method. This method must
be defined by all tasks (that is unless the :meth:`__call__` method
is overridden).
"""
__trace__ = None
__v2_compat__ = False # set by old base in celery.task.base
ErrorMail = ErrorMail
MaxRetriesExceededError = MaxRetriesExceededError
#: Execution strategy used, or the qualified name of one.
Strategy = 'celery.worker.strategy:default'
#: This is the instance bound to if the task is a method of a class.
__self__ = None
#: The application instance associated with this task class.
_app = None
#: Name of the task.
name = None
#: If :const:`True` the task is an abstract base class.
abstract = True
#: If disabled the worker will not forward magic keyword arguments.
#: Deprecated and scheduled for removal in v4.0.
accept_magic_kwargs = False
#: Maximum number of retries before giving up. If set to :const:`None`,
#: it will **never** stop retrying.
max_retries = 3
#: Default time in seconds before a retry of the task should be
#: executed. 3 minutes by default.
default_retry_delay = 3 * 60
#: Rate limit for this task type. Examples: :const:`None` (no rate
#: limit), `'100/s'` (hundred tasks a second), `'100/m'` (hundred tasks
#: a minute),`'100/h'` (hundred tasks an hour)
rate_limit = None
#: If enabled the worker will not store task state and return values
#: for this task. Defaults to the :setting:`CELERY_IGNORE_RESULT`
#: setting.
ignore_result = None
#: If enabled the request will keep track of subtasks started by
#: this task, and this information will be sent with the result
#: (``result.children``).
trail = True
#: If enabled the worker will send monitoring events related to
#: this task (but only if the worker is configured to send
#: task related events).
#: Note that this has no effect on the task-failure event case
#: where a task is not registered (as it will have no task class
#: to check this flag).
send_events = True
#: When enabled errors will be stored even if the task is otherwise
#: configured to ignore results.
store_errors_even_if_ignored = None
#: If enabled an email will be sent to :setting:`ADMINS` whenever a task
#: of this type fails.
send_error_emails = None
#: The name of a serializer that are registered with
#: :mod:`kombu.serialization.registry`. Default is `'pickle'`.
serializer = None
#: Hard time limit.
#: Defaults to the :setting:`CELERYD_TASK_TIME_LIMIT` setting.
time_limit = None
#: Soft time limit.
#: Defaults to the :setting:`CELERYD_TASK_SOFT_TIME_LIMIT` setting.
soft_time_limit = None
#: The result store backend used for this task.
backend = None
#: If disabled this task won't be registered automatically.
autoregister = True
#: If enabled the task will report its status as 'started' when the task
#: is executed by a worker. Disabled by default as the normal behaviour
#: is to not report that level of granularity. Tasks are either pending,
#: finished, or waiting to be retried.
#:
#: Having a 'started' status can be useful for when there are long
#: running tasks and there is a need to report which task is currently
#: running.
#:
#: The application default can be overridden using the
#: :setting:`CELERY_TRACK_STARTED` setting.
track_started = None
#: When enabled messages for this task will be acknowledged **after**
#: the task has been executed, and not *just before* which is the
#: default behavior.
#:
#: Please note that this means the task may be executed twice if the
#: worker crashes mid execution (which may be acceptable for some
#: applications).
#:
#: The application default can be overridden with the
#: :setting:`CELERY_ACKS_LATE` setting.
acks_late = None
#: Tuple of expected exceptions.
#:
#: These are errors that are expected in normal operation
#: and that should not be regarded as a real error by the worker.
#: Currently this means that the state will be updated to an error
#: state, but the worker will not log the event as an error.
throws = ()
#: Default task expiry time.
expires = None
#: Some may expect a request to exist even if the task has not been
#: called. This should probably be deprecated.
_default_request = None
_exec_options = None
__bound__ = False
from_config = (
('send_error_emails', 'CELERY_SEND_TASK_ERROR_EMAILS'),
('serializer', 'CELERY_TASK_SERIALIZER'),
('rate_limit', 'CELERY_DEFAULT_RATE_LIMIT'),
('track_started', 'CELERY_TRACK_STARTED'),
('acks_late', 'CELERY_ACKS_LATE'),
('ignore_result', 'CELERY_IGNORE_RESULT'),
('store_errors_even_if_ignored',
'CELERY_STORE_ERRORS_EVEN_IF_IGNORED'),
)
_backend = None # set by backend property.
__bound__ = False
# - Tasks are lazily bound, so that configuration is not set
# - until the task is actually used
@classmethod
def bind(self, app):
was_bound, self.__bound__ = self.__bound__, True
self._app = app
conf = app.conf
self._exec_options = None # clear option cache
for attr_name, config_name in self.from_config:
if getattr(self, attr_name, None) is None:
setattr(self, attr_name, conf[config_name])
if self.accept_magic_kwargs is None:
self.accept_magic_kwargs = app.accept_magic_kwargs
# decorate with annotations from config.
if not was_bound:
self.annotate()
from celery.utils.threads import LocalStack
self.request_stack = LocalStack()
# PeriodicTask uses this to add itself to the PeriodicTask schedule.
self.on_bound(app)
return app
@classmethod
def on_bound(self, app):
"""This method can be defined to do additional actions when the
task class is bound to an app."""
pass
@classmethod
def _get_app(self):
if self._app is None:
self._app = current_app
if not self.__bound__:
# The app property's __set__ method is not called
# if Task.app is set (on the class), so must bind on use.
self.bind(self._app)
return self._app
app = class_property(_get_app, bind)
@classmethod
def annotate(self):
for d in resolve_all_annotations(self.app.annotations, self):
for key, value in items(d):
if key.startswith('@'):
self.add_around(key[1:], value)
else:
setattr(self, key, value)
@classmethod
def add_around(self, attr, around):
orig = getattr(self, attr)
if getattr(orig, '__wrapped__', None):
orig = orig.__wrapped__
meth = around(orig)
meth.__wrapped__ = orig
setattr(self, attr, meth)
def __call__(self, *args, **kwargs):
_task_stack.push(self)
self.push_request()
try:
# add self if this is a bound task
if self.__self__ is not None:
return self.run(self.__self__, *args, **kwargs)
return self.run(*args, **kwargs)
finally:
self.pop_request()
_task_stack.pop()
def __reduce__(self):
# - tasks are pickled into the name of the task only, and the reciever
# - simply grabs it from the local registry.
# - in later versions the module of the task is also included,
# - and the receiving side tries to import that module so that
# - it will work even if the task has not been registered.
mod = type(self).__module__
mod = mod if mod and mod in sys.modules else None
return (_unpickle_task_v2, (self.name, mod), None)
def run(self, *args, **kwargs):
"""The body of the task executed by workers."""
raise NotImplementedError('Tasks must define the run method.')
def start_strategy(self, app, consumer, **kwargs):
return instantiate(self.Strategy, self, app, consumer, **kwargs)
def delay(self, *args, **kwargs):
"""Star argument version of :meth:`apply_async`.
Does not support the extra options enabled by :meth:`apply_async`.
:param \*args: positional arguments passed on to the task.
:param \*\*kwargs: keyword arguments passed on to the task.
:returns :class:`celery.result.AsyncResult`:
"""
return self.apply_async(args, kwargs)
def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
link=None, link_error=None, **options):
"""Apply tasks asynchronously by sending a message.
:keyword args: The positional arguments to pass on to the
task (a :class:`list` or :class:`tuple`).
:keyword kwargs: The keyword arguments to pass on to the
task (a :class:`dict`)
:keyword countdown: Number of seconds into the future that the
task should execute. Defaults to immediate
execution.
:keyword eta: A :class:`~datetime.datetime` object describing
the absolute time and date of when the task should
be executed. May not be specified if `countdown`
is also supplied.
:keyword expires: Either a :class:`int`, describing the number of
seconds, or a :class:`~datetime.datetime` object
that describes the absolute time and date of when
the task should expire. The task will not be
executed after the expiration time.
:keyword connection: Re-use existing broker connection instead
of establishing a new one.
:keyword retry: If enabled sending of the task message will be retried
in the event of connection loss or failure. Default
is taken from the :setting:`CELERY_TASK_PUBLISH_RETRY`
setting. Note that you need to handle the
producer/connection manually for this to work.
:keyword retry_policy: Override the retry policy used. See the
:setting:`CELERY_TASK_PUBLISH_RETRY_POLICY`
setting.
:keyword routing_key: Custom routing key used to route the task to a
worker server. If in combination with a
``queue`` argument only used to specify custom
routing keys to topic exchanges.
:keyword queue: The queue to route the task to. This must be a key
present in :setting:`CELERY_QUEUES`, or
:setting:`CELERY_CREATE_MISSING_QUEUES` must be
enabled. See :ref:`guide-routing` for more
information.
:keyword exchange: Named custom exchange to send the task to.
Usually not used in combination with the ``queue``
argument.
:keyword priority: The task priority, a number between 0 and 9.
Defaults to the :attr:`priority` attribute.
:keyword serializer: A string identifying the default
serialization method to use. Can be `pickle`,
`json`, `yaml`, `msgpack` or any custom
serialization method that has been registered
with :mod:`kombu.serialization.registry`.
Defaults to the :attr:`serializer` attribute.
:keyword compression: A string identifying the compression method
to use. Can be one of ``zlib``, ``bzip2``,
or any custom compression methods registered with
:func:`kombu.compression.register`. Defaults to
the :setting:`CELERY_MESSAGE_COMPRESSION`
setting.
:keyword link: A single, or a list of tasks to apply if the
task exits successfully.
:keyword link_error: A single, or a list of tasks to apply
if an error occurs while executing the task.
:keyword producer: :class:~@amqp.TaskProducer` instance to use.
:keyword add_to_parent: If set to True (default) and the task
is applied while executing another task, then the result
will be appended to the parent tasks ``request.children``
attribute. Trailing can also be disabled by default using the
:attr:`trail` attribute
:keyword publisher: Deprecated alias to ``producer``.
:keyword headers: Message headers to be sent in the
task (a :class:`dict`)
:rtype :class:`celery.result.AsyncResult`: if
:setting:`CELERY_ALWAYS_EAGER` is not set, otherwise
:class:`celery.result.EagerResult`.
Also supports all keyword arguments supported by
:meth:`kombu.Producer.publish`.
.. note::
If the :setting:`CELERY_ALWAYS_EAGER` setting is set, it will
be replaced by a local :func:`apply` call instead.
"""
app = self._get_app()
if app.conf.CELERY_ALWAYS_EAGER:
return self.apply(args, kwargs, task_id=task_id or uuid(),
link=link, link_error=link_error, **options)
# add 'self' if this is a "task_method".
if self.__self__ is not None:
args = args if isinstance(args, tuple) else tuple(args or ())
args = (self.__self__, ) + args
return app.send_task(
self.name, args, kwargs, task_id=task_id, producer=producer,
link=link, link_error=link_error, result_cls=self.AsyncResult,
**dict(self._get_exec_options(), **options)
)
def subtask_from_request(self, request=None, args=None, kwargs=None,
queue=None, **extra_options):
request = self.request if request is None else request
args = request.args if args is None else args
kwargs = request.kwargs if kwargs is None else kwargs
limit_hard, limit_soft = request.timelimit or (None, None)
options = {
'task_id': request.id,
'link': request.callbacks,
'link_error': request.errbacks,
'group_id': request.group,
'chord': request.chord,
'soft_time_limit': limit_soft,
'time_limit': limit_hard,
'reply_to': request.reply_to,
'headers': request.headers,
}
options.update(
{'queue': queue} if queue else (request.delivery_info or {})
)
return self.subtask(args, kwargs, options, type=self, **extra_options)
def retry(self, args=None, kwargs=None, exc=None, throw=True,
eta=None, countdown=None, max_retries=None, **options):
"""Retry the task.
:param args: Positional arguments to retry with.
:param kwargs: Keyword arguments to retry with.
:keyword exc: Custom exception to report when the max restart
limit has been exceeded (default:
:exc:`~@MaxRetriesExceededError`).
If this argument is set and retry is called while
an exception was raised (``sys.exc_info()`` is set)
it will attempt to reraise the current exception.
If no exception was raised it will raise the ``exc``
argument provided.
:keyword countdown: Time in seconds to delay the retry for.
:keyword eta: Explicit time and date to run the retry at
(must be a :class:`~datetime.datetime` instance).
:keyword max_retries: If set, overrides the default retry limit for
this execution. Changes to this parameter do not propagate to
subsequent task retry attempts. A value of :const:`None`, means
"use the default", so if you want infinite retries you would
have to set the :attr:`max_retries` attribute of the task to
:const:`None` first.
:keyword time_limit: If set, overrides the default time limit.
:keyword soft_time_limit: If set, overrides the default soft
time limit.
:keyword \*\*options: Any extra options to pass on to
meth:`apply_async`.
:keyword throw: If this is :const:`False`, do not raise the
:exc:`~@Retry` exception,
that tells the worker to mark the task as being
retried. Note that this means the task will be
marked as failed if the task raises an exception,
or successful if it returns.
:raises celery.exceptions.Retry: To tell the worker that
the task has been re-sent for retry. This always happens,
unless the `throw` keyword argument has been explicitly set
to :const:`False`, and is considered normal operation.
**Example**
.. code-block:: python
>>> from imaginary_twitter_lib import Twitter
>>> from proj.celery import app
>>> @app.task(bind=True)
... def tweet(self, auth, message):
... twitter = Twitter(oauth=auth)
... try:
... twitter.post_status_update(message)
... except twitter.FailWhale as exc:
... # Retry in 5 minutes.
... raise self.retry(countdown=60 * 5, exc=exc)
Although the task will never return above as `retry` raises an
exception to notify the worker, we use `raise` in front of the retry
to convey that the rest of the block will not be executed.
"""
request = self.request
retries = request.retries + 1
max_retries = self.max_retries if max_retries is None else max_retries
# Not in worker or emulated by (apply/always_eager),
# so just raise the original exception.
if request.called_directly:
maybe_reraise() # raise orig stack if PyErr_Occurred
raise exc or Retry('Task can be retried', None)
if not eta and countdown is None:
countdown = self.default_retry_delay
is_eager = request.is_eager
S = self.subtask_from_request(
request, args, kwargs,
countdown=countdown, eta=eta, retries=retries,
**options
)
if max_retries is not None and retries > max_retries:
if exc:
# first try to reraise the original exception
maybe_reraise()
# or if not in an except block then raise the custom exc.
raise exc
raise self.MaxRetriesExceededError(
"Can't retry {0}[{1}] args:{2} kwargs:{3}".format(
self.name, request.id, S.args, S.kwargs))
ret = Retry(exc=exc, when=eta or countdown)
if is_eager:
# if task was executed eagerly using apply(),
# then the retry must also be executed eagerly.
S.apply().get()
return ret
try:
S.apply_async()
except Exception as exc:
raise Reject(exc, requeue=False)
if throw:
raise ret
return ret
def apply(self, args=None, kwargs=None,
link=None, link_error=None, **options):
"""Execute this task locally, by blocking until the task returns.
:param args: positional arguments passed on to the task.
:param kwargs: keyword arguments passed on to the task.
:keyword throw: Re-raise task exceptions. Defaults to
the :setting:`CELERY_EAGER_PROPAGATES_EXCEPTIONS`
setting.
:rtype :class:`celery.result.EagerResult`:
"""
# trace imports Task, so need to import inline.
from celery.app.trace import eager_trace_task
app = self._get_app()
args = args or ()
# add 'self' if this is a bound method.
if self.__self__ is not None:
args = (self.__self__, ) + tuple(args)
kwargs = kwargs or {}
task_id = options.get('task_id') or uuid()
retries = options.get('retries', 0)
throw = app.either('CELERY_EAGER_PROPAGATES_EXCEPTIONS',
options.pop('throw', None))
# Make sure we get the task instance, not class.
task = app._tasks[self.name]
request = {'id': task_id,
'retries': retries,
'is_eager': True,
'logfile': options.get('logfile'),
'loglevel': options.get('loglevel', 0),
'callbacks': maybe_list(link),
'errbacks': maybe_list(link_error),
'headers': options.get('headers'),
'delivery_info': {'is_eager': True}}
if self.accept_magic_kwargs:
default_kwargs = {'task_name': task.name,
'task_id': task_id,
'task_retries': retries,
'task_is_eager': True,
'logfile': options.get('logfile'),
'loglevel': options.get('loglevel', 0),
'delivery_info': {'is_eager': True}}
supported_keys = fun_takes_kwargs(task.run, default_kwargs)
extend_with = dict((key, val)
for key, val in items(default_kwargs)
if key in supported_keys)
kwargs.update(extend_with)
tb = None
retval, info = eager_trace_task(task, task_id, args, kwargs,
app=self._get_app(),
request=request, propagate=throw)
if isinstance(retval, ExceptionInfo):
retval, tb = retval.exception, retval.traceback
state = states.SUCCESS if info is None else info.state
return EagerResult(task_id, retval, state, traceback=tb)
def AsyncResult(self, task_id, **kwargs):
"""Get AsyncResult instance for this kind of task.
:param task_id: Task id to get result for.
"""
return self._get_app().AsyncResult(task_id, backend=self.backend,
task_name=self.name, **kwargs)
def subtask(self, args=None, *starargs, **starkwargs):
"""Return :class:`~celery.signature` object for
this task, wrapping arguments and execution options
for a single task invocation."""
starkwargs.setdefault('app', self.app)
return signature(self, args, *starargs, **starkwargs)
def s(self, *args, **kwargs):
"""``.s(*a, **k) -> .subtask(a, k)``"""
return self.subtask(args, kwargs)
def si(self, *args, **kwargs):
"""``.si(*a, **k) -> .subtask(a, k, immutable=True)``"""
return self.subtask(args, kwargs, immutable=True)
def chunks(self, it, n):
"""Creates a :class:`~celery.canvas.chunks` task for this task."""
from celery import chunks
return chunks(self.s(), it, n, app=self.app)
def map(self, it):
"""Creates a :class:`~celery.canvas.xmap` task from ``it``."""
from celery import xmap
return xmap(self.s(), it, app=self.app)
def starmap(self, it):
"""Creates a :class:`~celery.canvas.xstarmap` task from ``it``."""
from celery import xstarmap
return xstarmap(self.s(), it, app=self.app)
def send_event(self, type_, **fields):
req = self.request
with self.app.events.default_dispatcher(hostname=req.hostname) as d:
return d.send(type_, uuid=req.id, **fields)
def update_state(self, task_id=None, state=None, meta=None):
"""Update task state.
:keyword task_id: Id of the task to update, defaults to the
id of the current task
:keyword state: New state (:class:`str`).
:keyword meta: State metadata (:class:`dict`).
"""
if task_id is None:
task_id = self.request.id
self.backend.store_result(task_id, meta, state)
def on_success(self, retval, task_id, args, kwargs):
"""Success handler.
Run by the worker if the task executes successfully.
:param retval: The return value of the task.
:param task_id: Unique id of the executed task.
:param args: Original arguments for the executed task.
:param kwargs: Original keyword arguments for the executed task.
The return value of this handler is ignored.
"""
pass
def on_retry(self, exc, task_id, args, kwargs, einfo):
"""Retry handler.
This is run by the worker when the task is to be retried.
:param exc: The exception sent to :meth:`retry`.
:param task_id: Unique id of the retried task.
:param args: Original arguments for the retried task.
:param kwargs: Original keyword arguments for the retried task.
:keyword einfo: :class:`~billiard.einfo.ExceptionInfo`
instance, containing the traceback.
The return value of this handler is ignored.
"""
pass
def on_failure(self, exc, task_id, args, kwargs, einfo):
"""Error handler.
This is run by the worker when the task fails.
:param exc: The exception raised by the task.
:param task_id: Unique id of the failed task.
:param args: Original arguments for the task that failed.
:param kwargs: Original keyword arguments for the task
that failed.
:keyword einfo: :class:`~billiard.einfo.ExceptionInfo`
instance, containing the traceback.
The return value of this handler is ignored.
"""
pass
def after_return(self, status, retval, task_id, args, kwargs, einfo):
"""Handler called after the task returns.
:param status: Current task state.
:param retval: Task return value/exception.
:param task_id: Unique id of the task.
:param args: Original arguments for the task.
:param kwargs: Original keyword arguments for the task.
:keyword einfo: :class:`~billiard.einfo.ExceptionInfo`
instance, containing the traceback (if any).
The return value of this handler is ignored.
"""
pass
def send_error_email(self, context, exc, **kwargs):
if self.send_error_emails and \
not getattr(self, 'disable_error_emails', None):
self.ErrorMail(self, **kwargs).send(context, exc)
def add_trail(self, result):
if self.trail:
self.request.children.append(result)
return result
def push_request(self, *args, **kwargs):
self.request_stack.push(Context(*args, **kwargs))
def pop_request(self):
self.request_stack.pop()
def __repr__(self):
"""`repr(task)`"""
return _reprtask(self, R_SELF_TASK if self.__self__ else R_INSTANCE)
def _get_request(self):
"""Get current request object."""
req = self.request_stack.top
if req is None:
# task was not called, but some may still expect a request
# to be there, perhaps that should be deprecated.
if self._default_request is None:
self._default_request = Context()
return self._default_request
return req
request = property(_get_request)
def _get_exec_options(self):
if self._exec_options is None:
self._exec_options = extract_exec_options(self)
return self._exec_options
@property
def backend(self):
backend = self._backend
if backend is None:
return self.app.backend
return backend
@backend.setter
def backend(self, value): # noqa
self._backend = value
@property
def __name__(self):
return self.__class__.__name__
BaseTask = Task # compat alias
@@ -1,441 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.trace
~~~~~~~~~~~~~~~~
This module defines how the task execution is traced:
errors are recorded, handlers are applied and so on.
"""
from __future__ import absolute_import
# ## ---
# This is the heart of the worker, the inner loop so to speak.
# It used to be split up into nice little classes and methods,
# but in the end it only resulted in bad performance and horrible tracebacks,
# so instead we now use one closure per task class.
import os
import socket
import sys
from warnings import warn
from billiard.einfo import ExceptionInfo
from kombu.exceptions import EncodeError
from kombu.utils import kwdict
from celery import current_app, group
from celery import states, signals
from celery._state import _task_stack
from celery.app import set_default_app
from celery.app.task import Task as BaseTask, Context
from celery.exceptions import Ignore, Reject, Retry
from celery.utils.log import get_logger
from celery.utils.objects import mro_lookup
from celery.utils.serialization import (
get_pickleable_exception,
get_pickleable_etype,
)
__all__ = ['TraceInfo', 'build_tracer', 'trace_task', 'eager_trace_task',
'setup_worker_optimizations', 'reset_worker_optimizations']
_logger = get_logger(__name__)
send_prerun = signals.task_prerun.send
send_postrun = signals.task_postrun.send
send_success = signals.task_success.send
STARTED = states.STARTED
SUCCESS = states.SUCCESS
IGNORED = states.IGNORED
REJECTED = states.REJECTED
RETRY = states.RETRY
FAILURE = states.FAILURE
EXCEPTION_STATES = states.EXCEPTION_STATES
IGNORE_STATES = frozenset([IGNORED, RETRY, REJECTED])
#: set by :func:`setup_worker_optimizations`
_tasks = None
_patched = {}
def task_has_custom(task, attr):
"""Return true if the task or one of its bases
defines ``attr`` (excluding the one in BaseTask)."""
return mro_lookup(task.__class__, attr, stop=(BaseTask, object),
monkey_patched=['celery.app.task'])
class TraceInfo(object):
__slots__ = ('state', 'retval')
def __init__(self, state, retval=None):
self.state = state
self.retval = retval
def handle_error_state(self, task, eager=False):
store_errors = not eager
if task.ignore_result:
store_errors = task.store_errors_even_if_ignored
return {
RETRY: self.handle_retry,
FAILURE: self.handle_failure,
}[self.state](task, store_errors=store_errors)
def handle_retry(self, task, store_errors=True):
"""Handle retry exception."""
# the exception raised is the Retry semi-predicate,
# and it's exc' attribute is the original exception raised (if any).
req = task.request
type_, _, tb = sys.exc_info()
try:
reason = self.retval
einfo = ExceptionInfo((type_, reason, tb))
if store_errors:
task.backend.mark_as_retry(
req.id, reason.exc, einfo.traceback, request=req,
)
task.on_retry(reason.exc, req.id, req.args, req.kwargs, einfo)
signals.task_retry.send(sender=task, request=req,
reason=reason, einfo=einfo)
return einfo
finally:
del(tb)
def handle_failure(self, task, store_errors=True):
"""Handle exception."""
req = task.request
type_, _, tb = sys.exc_info()
try:
exc = self.retval
einfo = ExceptionInfo()
einfo.exception = get_pickleable_exception(einfo.exception)
einfo.type = get_pickleable_etype(einfo.type)
if store_errors:
task.backend.mark_as_failure(
req.id, exc, einfo.traceback, request=req,
)
task.on_failure(exc, req.id, req.args, req.kwargs, einfo)
signals.task_failure.send(sender=task, task_id=req.id,
exception=exc, args=req.args,
kwargs=req.kwargs,
traceback=tb,
einfo=einfo)
return einfo
finally:
del(tb)
def build_tracer(name, task, loader=None, hostname=None, store_errors=True,
Info=TraceInfo, eager=False, propagate=False, app=None,
IGNORE_STATES=IGNORE_STATES):
"""Return a function that traces task execution; catches all
exceptions and updates result backend with the state and result
If the call was successful, it saves the result to the task result
backend, and sets the task status to `"SUCCESS"`.
If the call raises :exc:`~@Retry`, it extracts
the original exception, uses that as the result and sets the task state
to `"RETRY"`.
If the call results in an exception, it saves the exception as the task
result, and sets the task state to `"FAILURE"`.
Return a function that takes the following arguments:
:param uuid: The id of the task.
:param args: List of positional args to pass on to the function.
:param kwargs: Keyword arguments mapping to pass on to the function.
:keyword request: Request dict.
"""
# If the task doesn't define a custom __call__ method
# we optimize it away by simply calling the run method directly,
# saving the extra method call and a line less in the stack trace.
fun = task if task_has_custom(task, '__call__') else task.run
loader = loader or app.loader
backend = task.backend
ignore_result = task.ignore_result
track_started = task.track_started
track_started = not eager and (task.track_started and not ignore_result)
publish_result = not eager and not ignore_result
hostname = hostname or socket.gethostname()
loader_task_init = loader.on_task_init
loader_cleanup = loader.on_process_cleanup
task_on_success = None
task_after_return = None
if task_has_custom(task, 'on_success'):
task_on_success = task.on_success
if task_has_custom(task, 'after_return'):
task_after_return = task.after_return
store_result = backend.store_result
backend_cleanup = backend.process_cleanup
pid = os.getpid()
request_stack = task.request_stack
push_request = request_stack.push
pop_request = request_stack.pop
push_task = _task_stack.push
pop_task = _task_stack.pop
on_chord_part_return = backend.on_chord_part_return
prerun_receivers = signals.task_prerun.receivers
postrun_receivers = signals.task_postrun.receivers
success_receivers = signals.task_success.receivers
from celery import canvas
signature = canvas.maybe_signature # maybe_ does not clone if already
def on_error(request, exc, uuid, state=FAILURE, call_errbacks=True):
if propagate:
raise
I = Info(state, exc)
R = I.handle_error_state(task, eager=eager)
if call_errbacks:
group(
[signature(errback, app=app)
for errback in request.errbacks or []], app=app,
).apply_async((uuid, ))
return I, R, I.state, I.retval
def trace_task(uuid, args, kwargs, request=None):
# R - is the possibly prepared return value.
# I - is the Info object.
# retval - is the always unmodified return value.
# state - is the resulting task state.
# This function is very long because we have unrolled all the calls
# for performance reasons, and because the function is so long
# we want the main variables (I, and R) to stand out visually from the
# the rest of the variables, so breaking PEP8 is worth it ;)
R = I = retval = state = None
kwargs = kwdict(kwargs)
try:
push_task(task)
task_request = Context(request or {}, args=args,
called_directly=False, kwargs=kwargs)
push_request(task_request)
try:
# -*- PRE -*-
if prerun_receivers:
send_prerun(sender=task, task_id=uuid, task=task,
args=args, kwargs=kwargs)
loader_task_init(uuid, task)
if track_started:
store_result(
uuid, {'pid': pid, 'hostname': hostname}, STARTED,
request=task_request,
)
# -*- TRACE -*-
try:
R = retval = fun(*args, **kwargs)
state = SUCCESS
except Reject as exc:
I, R = Info(REJECTED, exc), ExceptionInfo(internal=True)
state, retval = I.state, I.retval
except Ignore as exc:
I, R = Info(IGNORED, exc), ExceptionInfo(internal=True)
state, retval = I.state, I.retval
except Retry as exc:
I, R, state, retval = on_error(
task_request, exc, uuid, RETRY, call_errbacks=False,
)
except Exception as exc:
I, R, state, retval = on_error(task_request, exc, uuid)
except BaseException as exc:
raise
else:
try:
# callback tasks must be applied before the result is
# stored, so that result.children is populated.
# groups are called inline and will store trail
# separately, so need to call them separately
# so that the trail's not added multiple times :(
# (Issue #1936)
callbacks = task.request.callbacks
if callbacks:
if len(task.request.callbacks) > 1:
sigs, groups = [], []
for sig in callbacks:
sig = signature(sig, app=app)
if isinstance(sig, group):
groups.append(sig)
else:
sigs.append(sig)
for group_ in groups:
group_.apply_async((retval, ))
if sigs:
group(sigs).apply_async((retval, ))
else:
signature(callbacks[0], app=app).delay(retval)
if publish_result:
store_result(
uuid, retval, SUCCESS, request=task_request,
)
except EncodeError as exc:
I, R, state, retval = on_error(task_request, exc, uuid)
else:
if task_on_success:
task_on_success(retval, uuid, args, kwargs)
if success_receivers:
send_success(sender=task, result=retval)
# -* POST *-
if state not in IGNORE_STATES:
if task_request.chord:
on_chord_part_return(task, state, R)
if task_after_return:
task_after_return(
state, retval, uuid, args, kwargs, None,
)
finally:
try:
if postrun_receivers:
send_postrun(sender=task, task_id=uuid, task=task,
args=args, kwargs=kwargs,
retval=retval, state=state)
finally:
pop_task()
pop_request()
if not eager:
try:
backend_cleanup()
loader_cleanup()
except (KeyboardInterrupt, SystemExit, MemoryError):
raise
except Exception as exc:
_logger.error('Process cleanup failed: %r', exc,
exc_info=True)
except MemoryError:
raise
except Exception as exc:
if eager:
raise
R = report_internal_error(task, exc)
return R, I
return trace_task
def trace_task(task, uuid, args, kwargs, request={}, **opts):
try:
if task.__trace__ is None:
task.__trace__ = build_tracer(task.name, task, **opts)
return task.__trace__(uuid, args, kwargs, request)[0]
except Exception as exc:
return report_internal_error(task, exc)
def _trace_task_ret(name, uuid, args, kwargs, request={}, app=None, **opts):
app = app or current_app
return trace_task(app.tasks[name],
uuid, args, kwargs, request, app=app, **opts)
trace_task_ret = _trace_task_ret
def _fast_trace_task(task, uuid, args, kwargs, request={}):
# setup_worker_optimizations will point trace_task_ret to here,
# so this is the function used in the worker.
return _tasks[task].__trace__(uuid, args, kwargs, request)[0]
def eager_trace_task(task, uuid, args, kwargs, request=None, **opts):
opts.setdefault('eager', True)
return build_tracer(task.name, task, **opts)(
uuid, args, kwargs, request)
def report_internal_error(task, exc):
_type, _value, _tb = sys.exc_info()
try:
_value = task.backend.prepare_exception(exc, 'pickle')
exc_info = ExceptionInfo((_type, _value, _tb), internal=True)
warn(RuntimeWarning(
'Exception raised outside body: {0!r}:\n{1}'.format(
exc, exc_info.traceback)))
return exc_info
finally:
del(_tb)
def setup_worker_optimizations(app):
global _tasks
global trace_task_ret
# make sure custom Task.__call__ methods that calls super
# will not mess up the request/task stack.
_install_stack_protection()
# all new threads start without a current app, so if an app is not
# passed on to the thread it will fall back to the "default app",
# which then could be the wrong app. So for the worker
# we set this to always return our app. This is a hack,
# and means that only a single app can be used for workers
# running in the same process.
app.set_current()
set_default_app(app)
# evaluate all task classes by finalizing the app.
app.finalize()
# set fast shortcut to task registry
_tasks = app._tasks
trace_task_ret = _fast_trace_task
from celery.worker import job as job_module
job_module.trace_task_ret = _fast_trace_task
job_module.__optimize__()
def reset_worker_optimizations():
global trace_task_ret
trace_task_ret = _trace_task_ret
try:
delattr(BaseTask, '_stackprotected')
except AttributeError:
pass
try:
BaseTask.__call__ = _patched.pop('BaseTask.__call__')
except KeyError:
pass
from celery.worker import job as job_module
job_module.trace_task_ret = _trace_task_ret
def _install_stack_protection():
# Patches BaseTask.__call__ in the worker to handle the edge case
# where people override it and also call super.
#
# - The worker optimizes away BaseTask.__call__ and instead
# calls task.run directly.
# - so with the addition of current_task and the request stack
# BaseTask.__call__ now pushes to those stacks so that
# they work when tasks are called directly.
#
# The worker only optimizes away __call__ in the case
# where it has not been overridden, so the request/task stack
# will blow if a custom task class defines __call__ and also
# calls super().
if not getattr(BaseTask, '_stackprotected', False):
_patched['BaseTask.__call__'] = orig = BaseTask.__call__
def __protected_call__(self, *args, **kwargs):
stack = self.request_stack
req = stack.top
if req and not req._protected and \
len(stack) == 1 and not req.called_directly:
req._protected = 1
return self.run(*args, **kwargs)
return orig(self, *args, **kwargs)
BaseTask.__call__ = __protected_call__
BaseTask._stackprotected = True
@@ -1,266 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.app.utils
~~~~~~~~~~~~~~~~
App utilities: Compat settings, bugreport tool, pickling apps.
"""
from __future__ import absolute_import
import os
import platform as _platform
import re
from collections import Mapping
from types import ModuleType
from kombu.utils.url import maybe_sanitize_url
from celery.datastructures import ConfigurationView
from celery.five import items, string_t, values
from celery.platforms import pyimplementation
from celery.utils.text import pretty
from celery.utils.imports import import_from_cwd, symbol_by_name, qualname
from .defaults import find
__all__ = ['Settings', 'appstr', 'bugreport',
'filter_hidden_settings', 'find_app']
#: Format used to generate bugreport information.
BUGREPORT_INFO = """
software -> celery:{celery_v} kombu:{kombu_v} py:{py_v}
billiard:{billiard_v} {driver_v}
platform -> system:{system} arch:{arch} imp:{py_i}
loader -> {loader}
settings -> transport:{transport} results:{results}
{human_settings}
"""
HIDDEN_SETTINGS = re.compile(
'API|TOKEN|KEY|SECRET|PASS|PROFANITIES_LIST|SIGNATURE|DATABASE',
re.IGNORECASE,
)
def appstr(app):
"""String used in __repr__ etc, to id app instances."""
return '{0}:0x{1:x}'.format(app.main or '__main__', id(app))
class Settings(ConfigurationView):
"""Celery settings object.
.. seealso:
:ref:`configuration` for a full list of configuration keys.
"""
@property
def CELERY_RESULT_BACKEND(self):
return self.first('CELERY_RESULT_BACKEND', 'CELERY_BACKEND')
@property
def BROKER_TRANSPORT(self):
return self.first('BROKER_TRANSPORT',
'BROKER_BACKEND', 'CARROT_BACKEND')
@property
def BROKER_BACKEND(self):
"""Deprecated compat alias to :attr:`BROKER_TRANSPORT`."""
return self.BROKER_TRANSPORT
@property
def BROKER_URL(self):
return (os.environ.get('CELERY_BROKER_URL') or
self.first('BROKER_URL', 'BROKER_HOST'))
@property
def CELERY_TIMEZONE(self):
# this way we also support django's time zone.
return self.first('CELERY_TIMEZONE', 'TIME_ZONE')
def without_defaults(self):
"""Return the current configuration, but without defaults."""
# the last stash is the default settings, so just skip that
return Settings({}, self._order[:-1])
def value_set_for(self, key):
return key in self.without_defaults()
def find_option(self, name, namespace='celery'):
"""Search for option by name.
Will return ``(namespace, key, type)`` tuple, e.g.::
>>> from proj.celery import app
>>> app.conf.find_option('disable_rate_limits')
('CELERY', 'DISABLE_RATE_LIMITS',
<Option: type->bool default->False>))
:param name: Name of option, cannot be partial.
:keyword namespace: Preferred namespace (``CELERY`` by default).
"""
return find(name, namespace)
def find_value_for_key(self, name, namespace='celery'):
"""Shortcut to ``get_by_parts(*find_option(name)[:-1])``"""
return self.get_by_parts(*self.find_option(name, namespace)[:-1])
def get_by_parts(self, *parts):
"""Return the current value for setting specified as a path.
Example::
>>> from proj.celery import app
>>> app.conf.get_by_parts('CELERY', 'DISABLE_RATE_LIMITS')
False
"""
return self['_'.join(part for part in parts if part)]
def table(self, with_defaults=False, censored=True):
filt = filter_hidden_settings if censored else lambda v: v
return filt(dict(
(k, v) for k, v in items(
self if with_defaults else self.without_defaults())
if k.isupper() and not k.startswith('_')
))
def humanize(self, with_defaults=False, censored=True):
"""Return a human readable string showing changes to the
configuration."""
return '\n'.join(
'{0}: {1}'.format(key, pretty(value, width=50))
for key, value in items(self.table(with_defaults, censored)))
class AppPickler(object):
"""Old application pickler/unpickler (< 3.1)."""
def __call__(self, cls, *args):
kwargs = self.build_kwargs(*args)
app = self.construct(cls, **kwargs)
self.prepare(app, **kwargs)
return app
def prepare(self, app, **kwargs):
app.conf.update(kwargs['changes'])
def build_kwargs(self, *args):
return self.build_standard_kwargs(*args)
def build_standard_kwargs(self, main, changes, loader, backend, amqp,
events, log, control, accept_magic_kwargs,
config_source=None):
return dict(main=main, loader=loader, backend=backend, amqp=amqp,
changes=changes, events=events, log=log, control=control,
set_as_current=False,
accept_magic_kwargs=accept_magic_kwargs,
config_source=config_source)
def construct(self, cls, **kwargs):
return cls(**kwargs)
def _unpickle_app(cls, pickler, *args):
"""Rebuild app for versions 2.5+"""
return pickler()(cls, *args)
def _unpickle_app_v2(cls, kwargs):
"""Rebuild app for versions 3.1+"""
kwargs['set_as_current'] = False
return cls(**kwargs)
def filter_hidden_settings(conf):
def maybe_censor(key, value, mask='*' * 8):
if isinstance(value, Mapping):
return filter_hidden_settings(value)
if isinstance(key, string_t):
if HIDDEN_SETTINGS.search(key):
return mask
elif 'BROKER_URL' in key.upper():
from kombu import Connection
return Connection(value).as_uri(mask=mask)
elif key.upper() in ('CELERY_RESULT_BACKEND', 'CELERY_BACKEND'):
return maybe_sanitize_url(value, mask=mask)
return value
return dict((k, maybe_censor(k, v)) for k, v in items(conf))
def bugreport(app):
"""Return a string containing information useful in bug reports."""
import billiard
import celery
import kombu
try:
conn = app.connection()
driver_v = '{0}:{1}'.format(conn.transport.driver_name,
conn.transport.driver_version())
transport = conn.transport_cls
except Exception:
transport = driver_v = ''
return BUGREPORT_INFO.format(
system=_platform.system(),
arch=', '.join(x for x in _platform.architecture() if x),
py_i=pyimplementation(),
celery_v=celery.VERSION_BANNER,
kombu_v=kombu.__version__,
billiard_v=billiard.__version__,
py_v=_platform.python_version(),
driver_v=driver_v,
transport=transport,
results=maybe_sanitize_url(
app.conf.CELERY_RESULT_BACKEND or 'disabled'),
human_settings=app.conf.humanize(),
loader=qualname(app.loader.__class__),
)
def find_app(app, symbol_by_name=symbol_by_name, imp=import_from_cwd):
from .base import Celery
try:
sym = symbol_by_name(app, imp=imp)
except AttributeError:
# last part was not an attribute, but a module
sym = imp(app)
if isinstance(sym, ModuleType) and ':' not in app:
try:
found = sym.app
if isinstance(found, ModuleType):
raise AttributeError()
except AttributeError:
try:
found = sym.celery
if isinstance(found, ModuleType):
raise AttributeError()
except AttributeError:
if getattr(sym, '__path__', None):
try:
return find_app(
'{0}.celery'.format(app),
symbol_by_name=symbol_by_name, imp=imp,
)
except ImportError:
pass
for suspect in values(vars(sym)):
if isinstance(suspect, Celery):
return suspect
raise
else:
return found
else:
return found
return sym
@@ -1,151 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.apps.beat
~~~~~~~~~~~~~~~~
This module is the 'program-version' of :mod:`celery.beat`.
It does everything necessary to run that module
as an actual application, like installing signal handlers
and so on.
"""
from __future__ import absolute_import, unicode_literals
import numbers
import socket
import sys
from celery import VERSION_BANNER, platforms, beat
from celery.utils.imports import qualname
from celery.utils.log import LOG_LEVELS, get_logger
from celery.utils.timeutils import humanize_seconds
__all__ = ['Beat']
STARTUP_INFO_FMT = """
Configuration ->
. broker -> {conninfo}
. loader -> {loader}
. scheduler -> {scheduler}
{scheduler_info}
. logfile -> {logfile}@%{loglevel}
. maxinterval -> {hmax_interval} ({max_interval}s)
""".strip()
logger = get_logger('celery.beat')
class Beat(object):
Service = beat.Service
app = None
def __init__(self, max_interval=None, app=None,
socket_timeout=30, pidfile=None, no_color=None,
loglevel=None, logfile=None, schedule=None,
scheduler_cls=None, redirect_stdouts=None,
redirect_stdouts_level=None, **kwargs):
"""Starts the beat task scheduler."""
self.app = app = app or self.app
self.loglevel = self._getopt('log_level', loglevel)
self.logfile = self._getopt('log_file', logfile)
self.schedule = self._getopt('schedule_filename', schedule)
self.scheduler_cls = self._getopt('scheduler', scheduler_cls)
self.redirect_stdouts = self._getopt(
'redirect_stdouts', redirect_stdouts,
)
self.redirect_stdouts_level = self._getopt(
'redirect_stdouts_level', redirect_stdouts_level,
)
self.max_interval = max_interval
self.socket_timeout = socket_timeout
self.no_color = no_color
self.colored = app.log.colored(
self.logfile,
enabled=not no_color if no_color is not None else no_color,
)
self.pidfile = pidfile
if not isinstance(self.loglevel, numbers.Integral):
self.loglevel = LOG_LEVELS[self.loglevel.upper()]
def _getopt(self, key, value):
if value is not None:
return value
return self.app.conf.find_value_for_key(key, namespace='celerybeat')
def run(self):
print(str(self.colored.cyan(
'celery beat v{0} is starting.'.format(VERSION_BANNER))))
self.init_loader()
self.set_process_title()
self.start_scheduler()
def setup_logging(self, colorize=None):
if colorize is None and self.no_color is not None:
colorize = not self.no_color
self.app.log.setup(self.loglevel, self.logfile,
self.redirect_stdouts, self.redirect_stdouts_level,
colorize=colorize)
def start_scheduler(self):
c = self.colored
if self.pidfile:
platforms.create_pidlock(self.pidfile)
beat = self.Service(app=self.app,
max_interval=self.max_interval,
scheduler_cls=self.scheduler_cls,
schedule_filename=self.schedule)
print(str(c.blue('__ ', c.magenta('-'),
c.blue(' ... __ '), c.magenta('-'),
c.blue(' _\n'),
c.reset(self.startup_info(beat)))))
self.setup_logging()
if self.socket_timeout:
logger.debug('Setting default socket timeout to %r',
self.socket_timeout)
socket.setdefaulttimeout(self.socket_timeout)
try:
self.install_sync_handler(beat)
beat.start()
except Exception as exc:
logger.critical('beat raised exception %s: %r',
exc.__class__, exc,
exc_info=True)
def init_loader(self):
# Run the worker init handler.
# (Usually imports task modules and such.)
self.app.loader.init_worker()
self.app.finalize()
def startup_info(self, beat):
scheduler = beat.get_scheduler(lazy=True)
return STARTUP_INFO_FMT.format(
conninfo=self.app.connection().as_uri(),
logfile=self.logfile or '[stderr]',
loglevel=LOG_LEVELS[self.loglevel],
loader=qualname(self.app.loader),
scheduler=qualname(scheduler),
scheduler_info=scheduler.info,
hmax_interval=humanize_seconds(beat.max_interval),
max_interval=beat.max_interval,
)
def set_process_title(self):
arg_start = 'manage' in sys.argv[0] and 2 or 1
platforms.set_process_title(
'celery beat', info=' '.join(sys.argv[arg_start:]),
)
def install_sync_handler(self, beat):
"""Install a `SIGTERM` + `SIGINT` handler that saves
the beat schedule."""
def _sync(signum, frame):
beat.sync()
raise SystemExit()
platforms.signals.update(SIGTERM=_sync, SIGINT=_sync)
@@ -1,372 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.apps.worker
~~~~~~~~~~~~~~~~~~
This module is the 'program-version' of :mod:`celery.worker`.
It does everything necessary to run that module
as an actual application, like installing signal handlers,
platform tweaks, and so on.
"""
from __future__ import absolute_import, print_function, unicode_literals
import logging
import os
import platform as _platform
import sys
import warnings
from functools import partial
from billiard import current_process
from kombu.utils.encoding import safe_str
from celery import VERSION_BANNER, platforms, signals
from celery.app import trace
from celery.exceptions import (
CDeprecationWarning, WorkerShutdown, WorkerTerminate,
)
from celery.five import string, string_t
from celery.loaders.app import AppLoader
from celery.platforms import check_privileges
from celery.utils import cry, isatty
from celery.utils.imports import qualname
from celery.utils.log import get_logger, in_sighandler, set_in_sighandler
from celery.utils.text import pluralize
from celery.worker import WorkController
__all__ = ['Worker']
logger = get_logger(__name__)
is_jython = sys.platform.startswith('java')
is_pypy = hasattr(sys, 'pypy_version_info')
W_PICKLE_DEPRECATED = """
Starting from version 3.2 Celery will refuse to accept pickle by default.
The pickle serializer is a security concern as it may give attackers
the ability to execute any command. It's important to secure
your broker from unauthorized access when using pickle, so we think
that enabling pickle should require a deliberate action and not be
the default choice.
If you depend on pickle then you should set a setting to disable this
warning and to be sure that everything will continue working
when you upgrade to Celery 3.2::
CELERY_ACCEPT_CONTENT = ['pickle', 'json', 'msgpack', 'yaml']
You must only enable the serializers that you will actually use.
"""
def active_thread_count():
from threading import enumerate
return sum(1 for t in enumerate()
if not t.name.startswith('Dummy-'))
def safe_say(msg):
print('\n{0}'.format(msg), file=sys.__stderr__)
ARTLINES = [
' --------------',
'---- **** -----',
'--- * *** * --',
'-- * - **** ---',
'- ** ----------',
'- ** ----------',
'- ** ----------',
'- ** ----------',
'- *** --- * ---',
'-- ******* ----',
'--- ***** -----',
' --------------',
]
BANNER = """\
{hostname} v{version}
{platform}
[config]
.> app: {app}
.> transport: {conninfo}
.> results: {results}
.> concurrency: {concurrency}
[queues]
{queues}
"""
EXTRA_INFO_FMT = """
[tasks]
{tasks}
"""
class Worker(WorkController):
def on_before_init(self, **kwargs):
trace.setup_worker_optimizations(self.app)
# this signal can be used to set up configuration for
# workers by name.
signals.celeryd_init.send(
sender=self.hostname, instance=self,
conf=self.app.conf, options=kwargs,
)
check_privileges(self.app.conf.CELERY_ACCEPT_CONTENT)
def on_after_init(self, purge=False, no_color=None,
redirect_stdouts=None, redirect_stdouts_level=None,
**kwargs):
self.redirect_stdouts = self._getopt(
'redirect_stdouts', redirect_stdouts,
)
self.redirect_stdouts_level = self._getopt(
'redirect_stdouts_level', redirect_stdouts_level,
)
super(Worker, self).setup_defaults(**kwargs)
self.purge = purge
self.no_color = no_color
self._isatty = isatty(sys.stdout)
self.colored = self.app.log.colored(
self.logfile,
enabled=not no_color if no_color is not None else no_color
)
def on_init_blueprint(self):
self._custom_logging = self.setup_logging()
# apply task execution optimizations
# -- This will finalize the app!
trace.setup_worker_optimizations(self.app)
def on_start(self):
if not self._custom_logging and self.redirect_stdouts:
self.app.log.redirect_stdouts(self.redirect_stdouts_level)
WorkController.on_start(self)
# this signal can be used to e.g. change queues after
# the -Q option has been applied.
signals.celeryd_after_setup.send(
sender=self.hostname, instance=self, conf=self.app.conf,
)
if not self.app.conf.value_set_for('CELERY_ACCEPT_CONTENT'):
warnings.warn(CDeprecationWarning(W_PICKLE_DEPRECATED))
if self.purge:
self.purge_messages()
# Dump configuration to screen so we have some basic information
# for when users sends bug reports.
print(safe_str(''.join([
string(self.colored.cyan(' \n', self.startup_info())),
string(self.colored.reset(self.extra_info() or '')),
])), file=sys.__stdout__)
self.set_process_status('-active-')
self.install_platform_tweaks(self)
def on_consumer_ready(self, consumer):
signals.worker_ready.send(sender=consumer)
print('{0} ready.'.format(safe_str(self.hostname), ))
def setup_logging(self, colorize=None):
if colorize is None and self.no_color is not None:
colorize = not self.no_color
return self.app.log.setup(
self.loglevel, self.logfile,
redirect_stdouts=False, colorize=colorize, hostname=self.hostname,
)
def purge_messages(self):
count = self.app.control.purge()
if count:
print('purge: Erased {0} {1} from the queue.\n'.format(
count, pluralize(count, 'message')))
def tasklist(self, include_builtins=True, sep='\n', int_='celery.'):
return sep.join(
' . {0}'.format(task) for task in sorted(self.app.tasks)
if (not task.startswith(int_) if not include_builtins else task)
)
def extra_info(self):
if self.loglevel <= logging.INFO:
include_builtins = self.loglevel <= logging.DEBUG
tasklist = self.tasklist(include_builtins=include_builtins)
return EXTRA_INFO_FMT.format(tasks=tasklist)
def startup_info(self):
app = self.app
concurrency = string(self.concurrency)
appr = '{0}:0x{1:x}'.format(app.main or '__main__', id(app))
if not isinstance(app.loader, AppLoader):
loader = qualname(app.loader)
if loader.startswith('celery.loaders'):
loader = loader[14:]
appr += ' ({0})'.format(loader)
if self.autoscale:
max, min = self.autoscale
concurrency = '{{min={0}, max={1}}}'.format(min, max)
pool = self.pool_cls
if not isinstance(pool, string_t):
pool = pool.__module__
concurrency += ' ({0})'.format(pool.split('.')[-1])
events = 'ON'
if not self.send_events:
events = 'OFF (enable -E to monitor this worker)'
banner = BANNER.format(
app=appr,
hostname=safe_str(self.hostname),
version=VERSION_BANNER,
conninfo=self.app.connection().as_uri(),
results=self.app.backend.as_uri(),
concurrency=concurrency,
platform=safe_str(_platform.platform()),
events=events,
queues=app.amqp.queues.format(indent=0, indent_first=False),
).splitlines()
# integrate the ASCII art.
for i, x in enumerate(banner):
try:
banner[i] = ' '.join([ARTLINES[i], banner[i]])
except IndexError:
banner[i] = ' ' * 16 + banner[i]
return '\n'.join(banner) + '\n'
def install_platform_tweaks(self, worker):
"""Install platform specific tweaks and workarounds."""
if self.app.IS_OSX:
self.osx_proxy_detection_workaround()
# Install signal handler so SIGHUP restarts the worker.
if not self._isatty:
# only install HUP handler if detached from terminal,
# so closing the terminal window doesn't restart the worker
# into the background.
if self.app.IS_OSX:
# OS X can't exec from a process using threads.
# See http://github.com/celery/celery/issues#issue/152
install_HUP_not_supported_handler(worker)
else:
install_worker_restart_handler(worker)
install_worker_term_handler(worker)
install_worker_term_hard_handler(worker)
install_worker_int_handler(worker)
install_cry_handler()
install_rdb_handler()
def osx_proxy_detection_workaround(self):
"""See http://github.com/celery/celery/issues#issue/161"""
os.environ.setdefault('celery_dummy_proxy', 'set_by_celeryd')
def set_process_status(self, info):
return platforms.set_mp_process_title(
'celeryd',
info='{0} ({1})'.format(info, platforms.strargv(sys.argv)),
hostname=self.hostname,
)
def _shutdown_handler(worker, sig='TERM', how='Warm',
exc=WorkerShutdown, callback=None):
def _handle_request(*args):
with in_sighandler():
from celery.worker import state
if current_process()._name == 'MainProcess':
if callback:
callback(worker)
safe_say('worker: {0} shutdown (MainProcess)'.format(how))
if active_thread_count() > 1:
setattr(state, {'Warm': 'should_stop',
'Cold': 'should_terminate'}[how], True)
else:
raise exc()
_handle_request.__name__ = str('worker_{0}'.format(how))
platforms.signals[sig] = _handle_request
install_worker_term_handler = partial(
_shutdown_handler, sig='SIGTERM', how='Warm', exc=WorkerShutdown,
)
if not is_jython: # pragma: no cover
install_worker_term_hard_handler = partial(
_shutdown_handler, sig='SIGQUIT', how='Cold', exc=WorkerTerminate,
)
else: # pragma: no cover
install_worker_term_handler = \
install_worker_term_hard_handler = lambda *a, **kw: None
def on_SIGINT(worker):
safe_say('worker: Hitting Ctrl+C again will terminate all running tasks!')
install_worker_term_hard_handler(worker, sig='SIGINT')
if not is_jython: # pragma: no cover
install_worker_int_handler = partial(
_shutdown_handler, sig='SIGINT', callback=on_SIGINT
)
else: # pragma: no cover
def install_worker_int_handler(*a, **kw):
pass
def _reload_current_worker():
platforms.close_open_fds([
sys.__stdin__, sys.__stdout__, sys.__stderr__,
])
os.execv(sys.executable, [sys.executable] + sys.argv)
def install_worker_restart_handler(worker, sig='SIGHUP'):
def restart_worker_sig_handler(*args):
"""Signal handler restarting the current python program."""
set_in_sighandler(True)
safe_say('Restarting celery worker ({0})'.format(' '.join(sys.argv)))
import atexit
atexit.register(_reload_current_worker)
from celery.worker import state
state.should_stop = True
platforms.signals[sig] = restart_worker_sig_handler
def install_cry_handler(sig='SIGUSR1'):
# Jython/PyPy does not have sys._current_frames
if is_jython or is_pypy: # pragma: no cover
return
def cry_handler(*args):
"""Signal handler logging the stacktrace of all active threads."""
with in_sighandler():
safe_say(cry())
platforms.signals[sig] = cry_handler
def install_rdb_handler(envvar='CELERY_RDBSIG',
sig='SIGUSR2'): # pragma: no cover
def rdb_handler(*args):
"""Signal handler setting a rdb breakpoint at the current frame."""
with in_sighandler():
from celery.contrib.rdb import set_trace, _frame
# gevent does not pass standard signal handler args
frame = args[1] if args else _frame().f_back
set_trace(frame)
if os.environ.get(envvar):
platforms.signals[sig] = rdb_handler
def install_HUP_not_supported_handler(worker, sig='SIGHUP'):
def warn_on_HUP_handler(signum, frame):
with in_sighandler():
safe_say('{sig} not supported: Restarting with {sig} is '
'unstable on this platform!'.format(sig=sig))
platforms.signals[sig] = warn_on_HUP_handler
@@ -1,68 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends
~~~~~~~~~~~~~~~
Backend abstract factory (...did I just say that?) and alias definitions.
"""
from __future__ import absolute_import
import sys
import types
from celery.exceptions import ImproperlyConfigured
from celery.local import Proxy
from celery._state import current_app
from celery.five import reraise
from celery.utils.imports import symbol_by_name
__all__ = ['get_backend_cls', 'get_backend_by_url']
UNKNOWN_BACKEND = """\
Unknown result backend: {0!r}. Did you spell that correctly? ({1!r})\
"""
BACKEND_ALIASES = {
'amqp': 'celery.backends.amqp:AMQPBackend',
'rpc': 'celery.backends.rpc.RPCBackend',
'cache': 'celery.backends.cache:CacheBackend',
'redis': 'celery.backends.redis:RedisBackend',
'mongodb': 'celery.backends.mongodb:MongoBackend',
'db': 'celery.backends.database:DatabaseBackend',
'database': 'celery.backends.database:DatabaseBackend',
'cassandra': 'celery.backends.cassandra:CassandraBackend',
'couchbase': 'celery.backends.couchbase:CouchBaseBackend',
'disabled': 'celery.backends.base:DisabledBackend',
}
#: deprecated alias to ``current_app.backend``.
default_backend = Proxy(lambda: current_app.backend)
def get_backend_cls(backend=None, loader=None):
"""Get backend class by name/alias"""
backend = backend or 'disabled'
loader = loader or current_app.loader
aliases = dict(BACKEND_ALIASES, **loader.override_backends)
try:
cls = symbol_by_name(backend, aliases)
except ValueError as exc:
reraise(ImproperlyConfigured, ImproperlyConfigured(
UNKNOWN_BACKEND.format(backend, exc)), sys.exc_info()[2])
if isinstance(cls, types.ModuleType):
raise ImproperlyConfigured(UNKNOWN_BACKEND.format(
backend, 'is a Python module, not a backend class.'))
return cls
def get_backend_by_url(backend=None, loader=None):
url = None
if backend and '://' in backend:
url = backend
scheme, _, _ = url.partition('://')
if '+' in scheme:
backend, url = url.split('+', 1)
else:
backend = scheme
return get_backend_cls(backend, loader), url
@@ -1,317 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.amqp
~~~~~~~~~~~~~~~~~~~~
The AMQP result backend.
This backend publishes results as messages.
"""
from __future__ import absolute_import
import socket
from collections import deque
from operator import itemgetter
from kombu import Exchange, Queue, Producer, Consumer
from celery import states
from celery.exceptions import TimeoutError
from celery.five import range, monotonic
from celery.utils.functional import dictfilter
from celery.utils.log import get_logger
from celery.utils.timeutils import maybe_s_to_ms
from .base import BaseBackend
__all__ = ['BacklogLimitExceeded', 'AMQPBackend']
logger = get_logger(__name__)
class BacklogLimitExceeded(Exception):
"""Too much state history to fast-forward."""
def repair_uuid(s):
# Historically the dashes in UUIDS are removed from AMQ entity names,
# but there is no known reason to. Hopefully we'll be able to fix
# this in v4.0.
return '%s-%s-%s-%s-%s' % (s[:8], s[8:12], s[12:16], s[16:20], s[20:])
class NoCacheQueue(Queue):
can_cache_declaration = False
class AMQPBackend(BaseBackend):
"""Publishes results by sending messages."""
Exchange = Exchange
Queue = NoCacheQueue
Consumer = Consumer
Producer = Producer
BacklogLimitExceeded = BacklogLimitExceeded
persistent = True
supports_autoexpire = True
supports_native_join = True
retry_policy = {
'max_retries': 20,
'interval_start': 0,
'interval_step': 1,
'interval_max': 1,
}
def __init__(self, app, connection=None, exchange=None, exchange_type=None,
persistent=None, serializer=None, auto_delete=True, **kwargs):
super(AMQPBackend, self).__init__(app, **kwargs)
conf = self.app.conf
self._connection = connection
self.persistent = self.prepare_persistent(persistent)
self.delivery_mode = 2 if self.persistent else 1
exchange = exchange or conf.CELERY_RESULT_EXCHANGE
exchange_type = exchange_type or conf.CELERY_RESULT_EXCHANGE_TYPE
self.exchange = self._create_exchange(
exchange, exchange_type, self.delivery_mode,
)
self.serializer = serializer or conf.CELERY_RESULT_SERIALIZER
self.auto_delete = auto_delete
self.expires = None
if 'expires' not in kwargs or kwargs['expires'] is not None:
self.expires = self.prepare_expires(kwargs.get('expires'))
self.queue_arguments = dictfilter({
'x-expires': maybe_s_to_ms(self.expires),
})
def _create_exchange(self, name, type='direct', delivery_mode=2):
return self.Exchange(name=name,
type=type,
delivery_mode=delivery_mode,
durable=self.persistent,
auto_delete=False)
def _create_binding(self, task_id):
name = self.rkey(task_id)
return self.Queue(name=name,
exchange=self.exchange,
routing_key=name,
durable=self.persistent,
auto_delete=self.auto_delete,
queue_arguments=self.queue_arguments)
def revive(self, channel):
pass
def rkey(self, task_id):
return task_id.replace('-', '')
def destination_for(self, task_id, request):
if request:
return self.rkey(task_id), request.correlation_id or task_id
return self.rkey(task_id), task_id
def store_result(self, task_id, result, status,
traceback=None, request=None, **kwargs):
"""Send task return value and status."""
routing_key, correlation_id = self.destination_for(task_id, request)
if not routing_key:
return
with self.app.amqp.producer_pool.acquire(block=True) as producer:
producer.publish(
{'task_id': task_id, 'status': status,
'result': self.encode_result(result, status),
'traceback': traceback,
'children': self.current_task_children(request)},
exchange=self.exchange,
routing_key=routing_key,
correlation_id=correlation_id,
serializer=self.serializer,
retry=True, retry_policy=self.retry_policy,
declare=self.on_reply_declare(task_id),
delivery_mode=self.delivery_mode,
)
return result
def on_reply_declare(self, task_id):
return [self._create_binding(task_id)]
def wait_for(self, task_id, timeout=None, cache=True,
no_ack=True, on_interval=None,
READY_STATES=states.READY_STATES,
PROPAGATE_STATES=states.PROPAGATE_STATES,
**kwargs):
cached_meta = self._cache.get(task_id)
if cache and cached_meta and \
cached_meta['status'] in READY_STATES:
return cached_meta
else:
try:
return self.consume(task_id, timeout=timeout, no_ack=no_ack,
on_interval=on_interval)
except socket.timeout:
raise TimeoutError('The operation timed out.')
def get_task_meta(self, task_id, backlog_limit=1000):
# Polling and using basic_get
with self.app.pool.acquire_channel(block=True) as (_, channel):
binding = self._create_binding(task_id)(channel)
binding.declare()
prev = latest = acc = None
for i in range(backlog_limit): # spool ffwd
acc = binding.get(
accept=self.accept, no_ack=False,
)
if not acc: # no more messages
break
if acc.payload['task_id'] == task_id:
prev, latest = latest, acc
if prev:
# backends are not expected to keep history,
# so we delete everything except the most recent state.
prev.ack()
prev = None
else:
raise self.BacklogLimitExceeded(task_id)
if latest:
payload = self._cache[task_id] = \
self.meta_from_decoded(latest.payload)
latest.requeue()
return payload
else:
# no new state, use previous
try:
return self._cache[task_id]
except KeyError:
# result probably pending.
return {'status': states.PENDING, 'result': None}
poll = get_task_meta # XXX compat
def drain_events(self, connection, consumer,
timeout=None, on_interval=None, now=monotonic, wait=None):
wait = wait or connection.drain_events
results = {}
def callback(meta, message):
if meta['status'] in states.READY_STATES:
results[meta['task_id']] = self.meta_from_decoded(meta)
consumer.callbacks[:] = [callback]
time_start = now()
while 1:
# Total time spent may exceed a single call to wait()
if timeout and now() - time_start >= timeout:
raise socket.timeout()
try:
wait(timeout=1)
except socket.timeout:
pass
if on_interval:
on_interval()
if results: # got event on the wanted channel.
break
self._cache.update(results)
return results
def consume(self, task_id, timeout=None, no_ack=True, on_interval=None):
wait = self.drain_events
with self.app.pool.acquire_channel(block=True) as (conn, channel):
binding = self._create_binding(task_id)
with self.Consumer(channel, binding,
no_ack=no_ack, accept=self.accept) as consumer:
while 1:
try:
return wait(
conn, consumer, timeout, on_interval)[task_id]
except KeyError:
continue
def _many_bindings(self, ids):
return [self._create_binding(task_id) for task_id in ids]
def get_many(self, task_ids, timeout=None, no_ack=True,
now=monotonic, getfields=itemgetter('status', 'task_id'),
READY_STATES=states.READY_STATES,
PROPAGATE_STATES=states.PROPAGATE_STATES, **kwargs):
with self.app.pool.acquire_channel(block=True) as (conn, channel):
ids = set(task_ids)
cached_ids = set()
mark_cached = cached_ids.add
for task_id in ids:
try:
cached = self._cache[task_id]
except KeyError:
pass
else:
if cached['status'] in READY_STATES:
yield task_id, cached
mark_cached(task_id)
ids.difference_update(cached_ids)
results = deque()
push_result = results.append
push_cache = self._cache.__setitem__
decode_result = self.meta_from_decoded
def on_message(message):
body = decode_result(message.decode())
state, uid = getfields(body)
if state in READY_STATES:
push_result(body) \
if uid in task_ids else push_cache(uid, body)
bindings = self._many_bindings(task_ids)
with self.Consumer(channel, bindings, on_message=on_message,
accept=self.accept, no_ack=no_ack):
wait = conn.drain_events
popleft = results.popleft
while ids:
wait(timeout=timeout)
while results:
state = popleft()
task_id = state['task_id']
ids.discard(task_id)
push_cache(task_id, state)
yield task_id, state
def reload_task_result(self, task_id):
raise NotImplementedError(
'reload_task_result is not supported by this backend.')
def reload_group_result(self, task_id):
"""Reload group result, even if it has been previously fetched."""
raise NotImplementedError(
'reload_group_result is not supported by this backend.')
def save_group(self, group_id, result):
raise NotImplementedError(
'save_group is not supported by this backend.')
def restore_group(self, group_id, cache=True):
raise NotImplementedError(
'restore_group is not supported by this backend.')
def delete_group(self, group_id):
raise NotImplementedError(
'delete_group is not supported by this backend.')
def as_uri(self, include_password=True):
return 'amqp://'
def __reduce__(self, args=(), kwargs={}):
kwargs.update(
connection=self._connection,
exchange=self.exchange.name,
exchange_type=self.exchange.type,
persistent=self.persistent,
serializer=self.serializer,
auto_delete=self.auto_delete,
expires=self.expires,
)
return super(AMQPBackend, self).__reduce__(args, kwargs)
@@ -1,623 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.base
~~~~~~~~~~~~~~~~~~~~
Result backend base classes.
- :class:`BaseBackend` defines the interface.
- :class:`KeyValueStoreBackend` is a common base class
using K/V semantics like _get and _put.
"""
from __future__ import absolute_import
import time
import sys
from datetime import timedelta
from billiard.einfo import ExceptionInfo
from kombu.serialization import (
dumps, loads, prepare_accept_content,
registry as serializer_registry,
)
from kombu.utils.encoding import bytes_to_str, ensure_bytes, from_utf8
from kombu.utils.url import maybe_sanitize_url
from celery import states
from celery import current_app, maybe_signature
from celery.app import current_task
from celery.exceptions import ChordError, TimeoutError, TaskRevokedError
from celery.five import items
from celery.result import (
GroupResult, ResultBase, allow_join_result, result_from_tuple,
)
from celery.utils import timeutils
from celery.utils.functional import LRUCache
from celery.utils.log import get_logger
from celery.utils.serialization import (
get_pickled_exception,
get_pickleable_exception,
create_exception_cls,
)
__all__ = ['BaseBackend', 'KeyValueStoreBackend', 'DisabledBackend']
EXCEPTION_ABLE_CODECS = frozenset(['pickle'])
PY3 = sys.version_info >= (3, 0)
logger = get_logger(__name__)
def unpickle_backend(cls, args, kwargs):
"""Return an unpickled backend."""
return cls(*args, app=current_app._get_current_object(), **kwargs)
class _nulldict(dict):
def ignore(self, *a, **kw):
pass
__setitem__ = update = setdefault = ignore
class BaseBackend(object):
READY_STATES = states.READY_STATES
UNREADY_STATES = states.UNREADY_STATES
EXCEPTION_STATES = states.EXCEPTION_STATES
TimeoutError = TimeoutError
#: Time to sleep between polling each individual item
#: in `ResultSet.iterate`. as opposed to the `interval`
#: argument which is for each pass.
subpolling_interval = None
#: If true the backend must implement :meth:`get_many`.
supports_native_join = False
#: If true the backend must automatically expire results.
#: The daily backend_cleanup periodic task will not be triggered
#: in this case.
supports_autoexpire = False
#: Set to true if the backend is peristent by default.
persistent = True
retry_policy = {
'max_retries': 20,
'interval_start': 0,
'interval_step': 1,
'interval_max': 1,
}
def __init__(self, app,
serializer=None, max_cached_results=None, accept=None,
url=None, **kwargs):
self.app = app
conf = self.app.conf
self.serializer = serializer or conf.CELERY_RESULT_SERIALIZER
(self.content_type,
self.content_encoding,
self.encoder) = serializer_registry._encoders[self.serializer]
cmax = max_cached_results or conf.CELERY_MAX_CACHED_RESULTS
self._cache = _nulldict() if cmax == -1 else LRUCache(limit=cmax)
self.accept = prepare_accept_content(
conf.CELERY_ACCEPT_CONTENT if accept is None else accept,
)
self.url = url
def as_uri(self, include_password=False):
"""Return the backend as an URI, sanitizing the password or not"""
# when using maybe_sanitize_url(), "/" is added
# we're stripping it for consistency
if include_password:
return self.url
url = maybe_sanitize_url(self.url or '')
return url[:-1] if url.endswith(':///') else url
def mark_as_started(self, task_id, **meta):
"""Mark a task as started"""
return self.store_result(task_id, meta, status=states.STARTED)
def mark_as_done(self, task_id, result, request=None):
"""Mark task as successfully executed."""
return self.store_result(task_id, result,
status=states.SUCCESS, request=request)
def mark_as_failure(self, task_id, exc, traceback=None, request=None):
"""Mark task as executed with failure. Stores the exception."""
return self.store_result(task_id, exc, status=states.FAILURE,
traceback=traceback, request=request)
def chord_error_from_stack(self, callback, exc=None):
from celery import group
app = self.app
backend = app._tasks[callback.task].backend
try:
group(
[app.signature(errback)
for errback in callback.options.get('link_error') or []],
app=app,
).apply_async((callback.id, ))
except Exception as eb_exc:
return backend.fail_from_current_stack(callback.id, exc=eb_exc)
else:
return backend.fail_from_current_stack(callback.id, exc=exc)
def fail_from_current_stack(self, task_id, exc=None):
type_, real_exc, tb = sys.exc_info()
try:
exc = real_exc if exc is None else exc
ei = ExceptionInfo((type_, exc, tb))
self.mark_as_failure(task_id, exc, ei.traceback)
return ei
finally:
del(tb)
def mark_as_retry(self, task_id, exc, traceback=None, request=None):
"""Mark task as being retries. Stores the current
exception (if any)."""
return self.store_result(task_id, exc, status=states.RETRY,
traceback=traceback, request=request)
def mark_as_revoked(self, task_id, reason='', request=None):
return self.store_result(task_id, TaskRevokedError(reason),
status=states.REVOKED, traceback=None,
request=request)
def prepare_exception(self, exc, serializer=None):
"""Prepare exception for serialization."""
serializer = self.serializer if serializer is None else serializer
if serializer in EXCEPTION_ABLE_CODECS:
return get_pickleable_exception(exc)
return {'exc_type': type(exc).__name__, 'exc_message': str(exc)}
def exception_to_python(self, exc):
"""Convert serialized exception to Python exception."""
if exc:
if not isinstance(exc, BaseException):
exc = create_exception_cls(
from_utf8(exc['exc_type']), __name__)(exc['exc_message'])
if self.serializer in EXCEPTION_ABLE_CODECS:
exc = get_pickled_exception(exc)
return exc
def prepare_value(self, result):
"""Prepare value for storage."""
if self.serializer != 'pickle' and isinstance(result, ResultBase):
return result.as_tuple()
return result
def encode(self, data):
_, _, payload = dumps(data, serializer=self.serializer)
return payload
def meta_from_decoded(self, meta):
if meta['status'] in self.EXCEPTION_STATES:
meta['result'] = self.exception_to_python(meta['result'])
return meta
def decode_result(self, payload):
return self.meta_from_decoded(self.decode(payload))
def decode(self, payload):
payload = PY3 and payload or str(payload)
return loads(payload,
content_type=self.content_type,
content_encoding=self.content_encoding,
accept=self.accept)
def wait_for(self, task_id,
timeout=None, interval=0.5, no_ack=True, on_interval=None):
"""Wait for task and return its result.
If the task raises an exception, this exception
will be re-raised by :func:`wait_for`.
If `timeout` is not :const:`None`, this raises the
:class:`celery.exceptions.TimeoutError` exception if the operation
takes longer than `timeout` seconds.
"""
time_elapsed = 0.0
while 1:
meta = self.get_task_meta(task_id)
if meta['status'] in states.READY_STATES:
return meta
if on_interval:
on_interval()
# avoid hammering the CPU checking status.
time.sleep(interval)
time_elapsed += interval
if timeout and time_elapsed >= timeout:
raise TimeoutError('The operation timed out.')
def prepare_expires(self, value, type=None):
if value is None:
value = self.app.conf.CELERY_TASK_RESULT_EXPIRES
if isinstance(value, timedelta):
value = timeutils.timedelta_seconds(value)
if value is not None and type:
return type(value)
return value
def prepare_persistent(self, enabled=None):
if enabled is not None:
return enabled
p = self.app.conf.CELERY_RESULT_PERSISTENT
return self.persistent if p is None else p
def encode_result(self, result, status):
if isinstance(result, ExceptionInfo):
result = result.exception
if status in self.EXCEPTION_STATES and isinstance(result, Exception):
return self.prepare_exception(result)
else:
return self.prepare_value(result)
def is_cached(self, task_id):
return task_id in self._cache
def store_result(self, task_id, result, status,
traceback=None, request=None, **kwargs):
"""Update task state and result."""
result = self.encode_result(result, status)
self._store_result(task_id, result, status, traceback,
request=request, **kwargs)
return result
def forget(self, task_id):
self._cache.pop(task_id, None)
self._forget(task_id)
def _forget(self, task_id):
raise NotImplementedError('backend does not implement forget.')
def get_status(self, task_id):
"""Get the status of a task."""
return self.get_task_meta(task_id)['status']
def get_traceback(self, task_id):
"""Get the traceback for a failed task."""
return self.get_task_meta(task_id).get('traceback')
def get_result(self, task_id):
"""Get the result of a task."""
return self.get_task_meta(task_id).get('result')
def get_children(self, task_id):
"""Get the list of subtasks sent by a task."""
try:
return self.get_task_meta(task_id)['children']
except KeyError:
pass
def get_task_meta(self, task_id, cache=True):
if cache:
try:
return self._cache[task_id]
except KeyError:
pass
meta = self._get_task_meta_for(task_id)
if cache and meta.get('status') == states.SUCCESS:
self._cache[task_id] = meta
return meta
def reload_task_result(self, task_id):
"""Reload task result, even if it has been previously fetched."""
self._cache[task_id] = self.get_task_meta(task_id, cache=False)
def reload_group_result(self, group_id):
"""Reload group result, even if it has been previously fetched."""
self._cache[group_id] = self.get_group_meta(group_id, cache=False)
def get_group_meta(self, group_id, cache=True):
if cache:
try:
return self._cache[group_id]
except KeyError:
pass
meta = self._restore_group(group_id)
if cache and meta is not None:
self._cache[group_id] = meta
return meta
def restore_group(self, group_id, cache=True):
"""Get the result for a group."""
meta = self.get_group_meta(group_id, cache=cache)
if meta:
return meta['result']
def save_group(self, group_id, result):
"""Store the result of an executed group."""
return self._save_group(group_id, result)
def delete_group(self, group_id):
self._cache.pop(group_id, None)
return self._delete_group(group_id)
def cleanup(self):
"""Backend cleanup. Is run by
:class:`celery.task.DeleteExpiredTaskMetaTask`."""
pass
def process_cleanup(self):
"""Cleanup actions to do at the end of a task worker process."""
pass
def on_task_call(self, producer, task_id):
return {}
def on_chord_part_return(self, task, state, result, propagate=False):
pass
def fallback_chord_unlock(self, group_id, body, result=None,
countdown=1, **kwargs):
kwargs['result'] = [r.as_tuple() for r in result]
self.app.tasks['celery.chord_unlock'].apply_async(
(group_id, body, ), kwargs, countdown=countdown,
)
def apply_chord(self, header, partial_args, group_id, body, **options):
result = header(*partial_args, task_id=group_id)
self.fallback_chord_unlock(group_id, body, **options)
return result
def current_task_children(self, request=None):
request = request or getattr(current_task(), 'request', None)
if request:
return [r.as_tuple() for r in getattr(request, 'children', [])]
def __reduce__(self, args=(), kwargs={}):
return (unpickle_backend, (self.__class__, args, kwargs))
BaseDictBackend = BaseBackend # XXX compat
class KeyValueStoreBackend(BaseBackend):
key_t = ensure_bytes
task_keyprefix = 'celery-task-meta-'
group_keyprefix = 'celery-taskset-meta-'
chord_keyprefix = 'chord-unlock-'
implements_incr = False
def __init__(self, *args, **kwargs):
if hasattr(self.key_t, '__func__'):
self.key_t = self.key_t.__func__ # remove binding
self._encode_prefixes()
super(KeyValueStoreBackend, self).__init__(*args, **kwargs)
if self.implements_incr:
self.apply_chord = self._apply_chord_incr
def _encode_prefixes(self):
self.task_keyprefix = self.key_t(self.task_keyprefix)
self.group_keyprefix = self.key_t(self.group_keyprefix)
self.chord_keyprefix = self.key_t(self.chord_keyprefix)
def get(self, key):
raise NotImplementedError('Must implement the get method.')
def mget(self, keys):
raise NotImplementedError('Does not support get_many')
def set(self, key, value):
raise NotImplementedError('Must implement the set method.')
def delete(self, key):
raise NotImplementedError('Must implement the delete method')
def incr(self, key):
raise NotImplementedError('Does not implement incr')
def expire(self, key, value):
pass
def get_key_for_task(self, task_id, key=''):
"""Get the cache key for a task by id."""
key_t = self.key_t
return key_t('').join([
self.task_keyprefix, key_t(task_id), key_t(key),
])
def get_key_for_group(self, group_id, key=''):
"""Get the cache key for a group by id."""
key_t = self.key_t
return key_t('').join([
self.group_keyprefix, key_t(group_id), key_t(key),
])
def get_key_for_chord(self, group_id, key=''):
"""Get the cache key for the chord waiting on group with given id."""
key_t = self.key_t
return key_t('').join([
self.chord_keyprefix, key_t(group_id), key_t(key),
])
def _strip_prefix(self, key):
"""Takes bytes, emits string."""
key = self.key_t(key)
for prefix in self.task_keyprefix, self.group_keyprefix:
if key.startswith(prefix):
return bytes_to_str(key[len(prefix):])
return bytes_to_str(key)
def _filter_ready(self, values, READY_STATES=states.READY_STATES):
for k, v in values:
if v is not None:
v = self.decode_result(v)
if v['status'] in READY_STATES:
yield k, v
def _mget_to_results(self, values, keys):
if hasattr(values, 'items'):
# client returns dict so mapping preserved.
return dict((self._strip_prefix(k), v)
for k, v in self._filter_ready(items(values)))
else:
# client returns list so need to recreate mapping.
return dict((bytes_to_str(keys[i]), v)
for i, v in self._filter_ready(enumerate(values)))
def get_many(self, task_ids, timeout=None, interval=0.5, no_ack=True,
READY_STATES=states.READY_STATES):
interval = 0.5 if interval is None else interval
ids = task_ids if isinstance(task_ids, set) else set(task_ids)
cached_ids = set()
cache = self._cache
for task_id in ids:
try:
cached = cache[task_id]
except KeyError:
pass
else:
if cached['status'] in READY_STATES:
yield bytes_to_str(task_id), cached
cached_ids.add(task_id)
ids.difference_update(cached_ids)
iterations = 0
while ids:
keys = list(ids)
r = self._mget_to_results(self.mget([self.get_key_for_task(k)
for k in keys]), keys)
cache.update(r)
ids.difference_update(set(bytes_to_str(v) for v in r))
for key, value in items(r):
yield bytes_to_str(key), value
if timeout and iterations * interval >= timeout:
raise TimeoutError('Operation timed out ({0})'.format(timeout))
time.sleep(interval) # don't busy loop.
iterations += 1
def _forget(self, task_id):
self.delete(self.get_key_for_task(task_id))
def _store_result(self, task_id, result, status,
traceback=None, request=None, **kwargs):
meta = {'status': status, 'result': result, 'traceback': traceback,
'children': self.current_task_children(request)}
self.set(self.get_key_for_task(task_id), self.encode(meta))
return result
def _save_group(self, group_id, result):
self.set(self.get_key_for_group(group_id),
self.encode({'result': result.as_tuple()}))
return result
def _delete_group(self, group_id):
self.delete(self.get_key_for_group(group_id))
def _get_task_meta_for(self, task_id):
"""Get task metadata for a task by id."""
meta = self.get(self.get_key_for_task(task_id))
if not meta:
return {'status': states.PENDING, 'result': None}
return self.decode_result(meta)
def _restore_group(self, group_id):
"""Get task metadata for a task by id."""
meta = self.get(self.get_key_for_group(group_id))
# previously this was always pickled, but later this
# was extended to support other serializers, so the
# structure is kind of weird.
if meta:
meta = self.decode(meta)
result = meta['result']
meta['result'] = result_from_tuple(result, self.app)
return meta
def _apply_chord_incr(self, header, partial_args, group_id, body,
result=None, **options):
self.save_group(group_id, self.app.GroupResult(group_id, result))
return header(*partial_args, task_id=group_id)
def on_chord_part_return(self, task, state, result, propagate=None):
if not self.implements_incr:
return
app = self.app
if propagate is None:
propagate = app.conf.CELERY_CHORD_PROPAGATES
gid = task.request.group
if not gid:
return
key = self.get_key_for_chord(gid)
try:
deps = GroupResult.restore(gid, backend=task.backend)
except Exception as exc:
callback = maybe_signature(task.request.chord, app=app)
logger.error('Chord %r raised: %r', gid, exc, exc_info=1)
return self.chord_error_from_stack(
callback,
ChordError('Cannot restore group: {0!r}'.format(exc)),
)
if deps is None:
try:
raise ValueError(gid)
except ValueError as exc:
callback = maybe_signature(task.request.chord, app=app)
logger.error('Chord callback %r raised: %r', gid, exc,
exc_info=1)
return self.chord_error_from_stack(
callback,
ChordError('GroupResult {0} no longer exists'.format(gid)),
)
val = self.incr(key)
size = len(deps)
if val > size:
logger.warning('Chord counter incremented too many times for %r',
gid)
elif val == size:
callback = maybe_signature(task.request.chord, app=app)
j = deps.join_native if deps.supports_native_join else deps.join
try:
with allow_join_result():
ret = j(timeout=3.0, propagate=propagate)
except Exception as exc:
try:
culprit = next(deps._failed_join_report())
reason = 'Dependency {0.id} raised {1!r}'.format(
culprit, exc,
)
except StopIteration:
reason = repr(exc)
logger.error('Chord %r raised: %r', gid, reason, exc_info=1)
self.chord_error_from_stack(callback, ChordError(reason))
else:
try:
callback.delay(ret)
except Exception as exc:
logger.error('Chord %r raised: %r', gid, exc, exc_info=1)
self.chord_error_from_stack(
callback,
ChordError('Callback error: {0!r}'.format(exc)),
)
finally:
deps.delete()
self.client.delete(key)
else:
self.expire(key, 86400)
class DisabledBackend(BaseBackend):
_cache = {} # need this attribute to reset cache in tests.
def store_result(self, *args, **kwargs):
pass
def _is_disabled(self, *args, **kwargs):
raise NotImplementedError(
'No result backend configured. '
'Please see the documentation for more information.')
def as_uri(self, *args, **kwargs):
return 'disabled://'
get_state = get_status = get_result = get_traceback = _is_disabled
wait_for = get_many = _is_disabled
@@ -1,161 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.cache
~~~~~~~~~~~~~~~~~~~~~
Memcache and in-memory cache result backend.
"""
from __future__ import absolute_import
import sys
from kombu.utils import cached_property
from kombu.utils.encoding import bytes_to_str, ensure_bytes
from celery.exceptions import ImproperlyConfigured
from celery.utils.functional import LRUCache
from .base import KeyValueStoreBackend
__all__ = ['CacheBackend']
_imp = [None]
PY3 = sys.version_info[0] == 3
REQUIRES_BACKEND = """\
The memcached backend requires either pylibmc or python-memcached.\
"""
UNKNOWN_BACKEND = """\
The cache backend {0!r} is unknown,
Please use one of the following backends instead: {1}\
"""
def import_best_memcache():
if _imp[0] is None:
is_pylibmc, memcache_key_t = False, ensure_bytes
try:
import pylibmc as memcache
is_pylibmc = True
except ImportError:
try:
import memcache # noqa
except ImportError:
raise ImproperlyConfigured(REQUIRES_BACKEND)
if PY3:
memcache_key_t = bytes_to_str
_imp[0] = (is_pylibmc, memcache, memcache_key_t)
return _imp[0]
def get_best_memcache(*args, **kwargs):
is_pylibmc, memcache, key_t = import_best_memcache()
Client = _Client = memcache.Client
if not is_pylibmc:
def Client(*args, **kwargs): # noqa
kwargs.pop('behaviors', None)
return _Client(*args, **kwargs)
return Client, key_t
class DummyClient(object):
def __init__(self, *args, **kwargs):
self.cache = LRUCache(limit=5000)
def get(self, key, *args, **kwargs):
return self.cache.get(key)
def get_multi(self, keys):
cache = self.cache
return dict((k, cache[k]) for k in keys if k in cache)
def set(self, key, value, *args, **kwargs):
self.cache[key] = value
def delete(self, key, *args, **kwargs):
self.cache.pop(key, None)
def incr(self, key, delta=1):
return self.cache.incr(key, delta)
backends = {'memcache': get_best_memcache,
'memcached': get_best_memcache,
'pylibmc': get_best_memcache,
'memory': lambda: (DummyClient, ensure_bytes)}
class CacheBackend(KeyValueStoreBackend):
servers = None
supports_autoexpire = True
supports_native_join = True
implements_incr = True
def __init__(self, app, expires=None, backend=None,
options={}, url=None, **kwargs):
super(CacheBackend, self).__init__(app, **kwargs)
self.url = url
self.options = dict(self.app.conf.CELERY_CACHE_BACKEND_OPTIONS,
**options)
self.backend = url or backend or self.app.conf.CELERY_CACHE_BACKEND
if self.backend:
self.backend, _, servers = self.backend.partition('://')
self.servers = servers.rstrip('/').split(';')
self.expires = self.prepare_expires(expires, type=int)
try:
self.Client, self.key_t = backends[self.backend]()
except KeyError:
raise ImproperlyConfigured(UNKNOWN_BACKEND.format(
self.backend, ', '.join(backends)))
self._encode_prefixes() # rencode the keyprefixes
def get(self, key):
return self.client.get(key)
def mget(self, keys):
return self.client.get_multi(keys)
def set(self, key, value):
return self.client.set(key, value, self.expires)
def delete(self, key):
return self.client.delete(key)
def _apply_chord_incr(self, header, partial_args, group_id, body, **opts):
self.client.set(self.get_key_for_chord(group_id), 0, time=86400)
return super(CacheBackend, self)._apply_chord_incr(
header, partial_args, group_id, body, **opts
)
def incr(self, key):
return self.client.incr(key)
@cached_property
def client(self):
return self.Client(self.servers, **self.options)
def __reduce__(self, args=(), kwargs={}):
servers = ';'.join(self.servers)
backend = '{0}://{1}/'.format(self.backend, servers)
kwargs.update(
dict(backend=backend,
expires=self.expires,
options=self.options))
return super(CacheBackend, self).__reduce__(args, kwargs)
def as_uri(self, *args, **kwargs):
"""Return the backend as an URI.
This properly handles the case of multiple servers.
"""
servers = ';'.join(self.servers)
return '{0}://{1}/'.format(self.backend, servers)
@@ -1,196 +0,0 @@
# -* coding: utf-8 -*-
"""
celery.backends.cassandra
~~~~~~~~~~~~~~~~~~~~~~~~~
Apache Cassandra result store backend.
"""
from __future__ import absolute_import
try: # pragma: no cover
import pycassa
from thrift import Thrift
C = pycassa.cassandra.ttypes
except ImportError: # pragma: no cover
pycassa = None # noqa
import socket
import time
from celery import states
from celery.exceptions import ImproperlyConfigured
from celery.five import monotonic
from celery.utils.log import get_logger
from celery.utils.timeutils import maybe_timedelta, timedelta_seconds
from .base import BaseBackend
__all__ = ['CassandraBackend']
logger = get_logger(__name__)
class CassandraBackend(BaseBackend):
"""Highly fault tolerant Cassandra backend.
.. attribute:: servers
List of Cassandra servers with format: ``hostname:port``.
:raises celery.exceptions.ImproperlyConfigured: if
module :mod:`pycassa` is not available.
"""
servers = []
keyspace = None
column_family = None
detailed_mode = False
_retry_timeout = 300
_retry_wait = 3
supports_autoexpire = True
def __init__(self, servers=None, keyspace=None, column_family=None,
cassandra_options=None, detailed_mode=False, **kwargs):
"""Initialize Cassandra backend.
Raises :class:`celery.exceptions.ImproperlyConfigured` if
the :setting:`CASSANDRA_SERVERS` setting is not set.
"""
super(CassandraBackend, self).__init__(**kwargs)
self.expires = kwargs.get('expires') or maybe_timedelta(
self.app.conf.CELERY_TASK_RESULT_EXPIRES)
if not pycassa:
raise ImproperlyConfigured(
'You need to install the pycassa library to use the '
'Cassandra backend. See https://github.com/pycassa/pycassa')
conf = self.app.conf
self.servers = (servers or
conf.get('CASSANDRA_SERVERS') or
self.servers)
self.keyspace = (keyspace or
conf.get('CASSANDRA_KEYSPACE') or
self.keyspace)
self.column_family = (column_family or
conf.get('CASSANDRA_COLUMN_FAMILY') or
self.column_family)
self.cassandra_options = dict(conf.get('CASSANDRA_OPTIONS') or {},
**cassandra_options or {})
self.detailed_mode = (detailed_mode or
conf.get('CASSANDRA_DETAILED_MODE') or
self.detailed_mode)
read_cons = conf.get('CASSANDRA_READ_CONSISTENCY') or 'LOCAL_QUORUM'
write_cons = conf.get('CASSANDRA_WRITE_CONSISTENCY') or 'LOCAL_QUORUM'
try:
self.read_consistency = getattr(pycassa.ConsistencyLevel,
read_cons)
except AttributeError:
self.read_consistency = pycassa.ConsistencyLevel.LOCAL_QUORUM
try:
self.write_consistency = getattr(pycassa.ConsistencyLevel,
write_cons)
except AttributeError:
self.write_consistency = pycassa.ConsistencyLevel.LOCAL_QUORUM
if not self.servers or not self.keyspace or not self.column_family:
raise ImproperlyConfigured(
'Cassandra backend not configured.')
self._column_family = None
def _retry_on_error(self, fun, *args, **kwargs):
ts = monotonic() + self._retry_timeout
while 1:
try:
return fun(*args, **kwargs)
except (pycassa.InvalidRequestException,
pycassa.TimedOutException,
pycassa.UnavailableException,
pycassa.AllServersUnavailable,
socket.error,
socket.timeout,
Thrift.TException) as exc:
if monotonic() > ts:
raise
logger.warning('Cassandra error: %r. Retrying...', exc)
time.sleep(self._retry_wait)
def _get_column_family(self):
if self._column_family is None:
conn = pycassa.ConnectionPool(self.keyspace,
server_list=self.servers,
**self.cassandra_options)
self._column_family = pycassa.ColumnFamily(
conn, self.column_family,
read_consistency_level=self.read_consistency,
write_consistency_level=self.write_consistency,
)
return self._column_family
def process_cleanup(self):
if self._column_family is not None:
self._column_family = None
def _store_result(self, task_id, result, status,
traceback=None, request=None, **kwargs):
"""Store return value and status of an executed task."""
def _do_store():
cf = self._get_column_family()
date_done = self.app.now()
meta = {'status': status,
'date_done': date_done.strftime('%Y-%m-%dT%H:%M:%SZ'),
'traceback': self.encode(traceback),
'result': self.encode(result),
'children': self.encode(
self.current_task_children(request),
)}
if self.detailed_mode:
cf.insert(task_id, {date_done: self.encode(meta)},
ttl=self.expires and timedelta_seconds(self.expires))
else:
cf.insert(task_id, meta,
ttl=self.expires and timedelta_seconds(self.expires))
return self._retry_on_error(_do_store)
def as_uri(self, include_password=True):
return 'cassandra://'
def _get_task_meta_for(self, task_id):
"""Get task metadata for a task by id."""
def _do_get():
cf = self._get_column_family()
try:
if self.detailed_mode:
row = cf.get(task_id, column_reversed=True, column_count=1)
obj = self.decode(list(row.values())[0])
else:
obj = cf.get(task_id)
meta = {
'task_id': task_id,
'status': obj['status'],
'result': self.decode(obj['result']),
'date_done': obj['date_done'],
'traceback': self.decode(obj['traceback']),
'children': self.decode(obj['children']),
}
except (KeyError, pycassa.NotFoundException):
meta = {'status': states.PENDING, 'result': None}
return meta
return self._retry_on_error(_do_get)
def __reduce__(self, args=(), kwargs={}):
kwargs.update(
dict(servers=self.servers,
keyspace=self.keyspace,
column_family=self.column_family,
cassandra_options=self.cassandra_options))
return super(CassandraBackend, self).__reduce__(args, kwargs)
@@ -1,116 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.couchbase
~~~~~~~~~~~~~~~~~~~~~~~~~
CouchBase result store backend.
"""
from __future__ import absolute_import
import logging
try:
from couchbase import Couchbase
from couchbase.connection import Connection
from couchbase.exceptions import NotFoundError
except ImportError:
Couchbase = Connection = NotFoundError = None # noqa
from kombu.utils.url import _parse_url
from celery.exceptions import ImproperlyConfigured
from celery.utils.timeutils import maybe_timedelta
from .base import KeyValueStoreBackend
__all__ = ['CouchBaseBackend']
class CouchBaseBackend(KeyValueStoreBackend):
"""CouchBase backend.
:raises celery.exceptions.ImproperlyConfigured: if
module :mod:`couchbase` is not available.
"""
bucket = 'default'
host = 'localhost'
port = 8091
username = None
password = None
quiet = False
conncache = None
unlock_gil = True
timeout = 2.5
transcoder = None
def __init__(self, url=None, *args, **kwargs):
super(CouchBaseBackend, self).__init__(*args, **kwargs)
self.url = url
self.expires = kwargs.get('expires') or maybe_timedelta(
self.app.conf.CELERY_TASK_RESULT_EXPIRES)
if Couchbase is None:
raise ImproperlyConfigured(
'You need to install the couchbase library to use the '
'CouchBase backend.',
)
uhost = uport = uname = upass = ubucket = None
if url:
_, uhost, uport, uname, upass, ubucket, _ = _parse_url(url)
ubucket = ubucket.strip('/') if ubucket else None
config = self.app.conf.get('CELERY_COUCHBASE_BACKEND_SETTINGS', None)
if config is not None:
if not isinstance(config, dict):
raise ImproperlyConfigured(
'Couchbase backend settings should be grouped in a dict',
)
else:
config = {}
self.host = uhost or config.get('host', self.host)
self.port = int(uport or config.get('port', self.port))
self.bucket = ubucket or config.get('bucket', self.bucket)
self.username = uname or config.get('username', self.username)
self.password = upass or config.get('password', self.password)
self._connection = None
def _get_connection(self):
"""Connect to the Couchbase server."""
if self._connection is None:
kwargs = {'bucket': self.bucket, 'host': self.host}
if self.port:
kwargs.update({'port': self.port})
if self.username:
kwargs.update({'username': self.username})
if self.password:
kwargs.update({'password': self.password})
logging.debug('couchbase settings %r', kwargs)
self._connection = Connection(**kwargs)
return self._connection
@property
def connection(self):
return self._get_connection()
def get(self, key):
try:
return self.connection.get(key).value
except NotFoundError:
return None
def set(self, key, value):
self.connection.set(key, value)
def mget(self, keys):
return [self.get(key) for key in keys]
def delete(self, key):
self.connection.delete(key)
@@ -1,201 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.database
~~~~~~~~~~~~~~~~~~~~~~~~
SQLAlchemy result store backend.
"""
from __future__ import absolute_import
import logging
from contextlib import contextmanager
from functools import wraps
from celery import states
from celery.backends.base import BaseBackend
from celery.exceptions import ImproperlyConfigured
from celery.five import range
from celery.utils.timeutils import maybe_timedelta
from .models import Task
from .models import TaskSet
from .session import SessionManager
logger = logging.getLogger(__name__)
__all__ = ['DatabaseBackend']
def _sqlalchemy_installed():
try:
import sqlalchemy
except ImportError:
raise ImproperlyConfigured(
'The database result backend requires SQLAlchemy to be installed.'
'See http://pypi.python.org/pypi/SQLAlchemy')
return sqlalchemy
_sqlalchemy_installed()
from sqlalchemy.exc import DatabaseError, InvalidRequestError # noqa
from sqlalchemy.orm.exc import StaleDataError # noqa
@contextmanager
def session_cleanup(session):
try:
yield
except Exception:
session.rollback()
raise
finally:
session.close()
def retry(fun):
@wraps(fun)
def _inner(*args, **kwargs):
max_retries = kwargs.pop('max_retries', 3)
for retries in range(max_retries):
try:
return fun(*args, **kwargs)
except (DatabaseError, InvalidRequestError, StaleDataError):
logger.warning(
"Failed operation %s. Retrying %s more times.",
fun.__name__, max_retries - retries - 1,
exc_info=True,
)
if retries + 1 >= max_retries:
raise
return _inner
class DatabaseBackend(BaseBackend):
"""The database result backend."""
# ResultSet.iterate should sleep this much between each pool,
# to not bombard the database with queries.
subpolling_interval = 0.5
def __init__(self, dburi=None, expires=None,
engine_options=None, url=None, **kwargs):
# The `url` argument was added later and is used by
# the app to set backend by url (celery.backends.get_backend_by_url)
super(DatabaseBackend, self).__init__(**kwargs)
conf = self.app.conf
self.expires = maybe_timedelta(self.prepare_expires(expires))
self.url = url or dburi or conf.CELERY_RESULT_DBURI
self.engine_options = dict(
engine_options or {},
**conf.CELERY_RESULT_ENGINE_OPTIONS or {})
self.short_lived_sessions = kwargs.get(
'short_lived_sessions',
conf.CELERY_RESULT_DB_SHORT_LIVED_SESSIONS,
)
tablenames = conf.CELERY_RESULT_DB_TABLENAMES or {}
Task.__table__.name = tablenames.get('task', 'celery_taskmeta')
TaskSet.__table__.name = tablenames.get('group', 'celery_tasksetmeta')
if not self.url:
raise ImproperlyConfigured(
'Missing connection string! Do you have '
'CELERY_RESULT_DBURI set to a real value?')
def ResultSession(self, session_manager=SessionManager()):
return session_manager.session_factory(
dburi=self.url,
short_lived_sessions=self.short_lived_sessions,
**self.engine_options
)
@retry
def _store_result(self, task_id, result, status,
traceback=None, max_retries=3, **kwargs):
"""Store return value and status of an executed task."""
session = self.ResultSession()
with session_cleanup(session):
task = list(session.query(Task).filter(Task.task_id == task_id))
task = task and task[0]
if not task:
task = Task(task_id)
session.add(task)
session.flush()
task.result = result
task.status = status
task.traceback = traceback
session.commit()
return result
@retry
def _get_task_meta_for(self, task_id):
"""Get task metadata for a task by id."""
session = self.ResultSession()
with session_cleanup(session):
task = list(session.query(Task).filter(Task.task_id == task_id))
task = task and task[0]
if not task:
task = Task(task_id)
task.status = states.PENDING
task.result = None
return self.meta_from_decoded(task.to_dict())
@retry
def _save_group(self, group_id, result):
"""Store the result of an executed group."""
session = self.ResultSession()
with session_cleanup(session):
group = TaskSet(group_id, result)
session.add(group)
session.flush()
session.commit()
return result
@retry
def _restore_group(self, group_id):
"""Get metadata for group by id."""
session = self.ResultSession()
with session_cleanup(session):
group = session.query(TaskSet).filter(
TaskSet.taskset_id == group_id).first()
if group:
return group.to_dict()
@retry
def _delete_group(self, group_id):
"""Delete metadata for group by id."""
session = self.ResultSession()
with session_cleanup(session):
session.query(TaskSet).filter(
TaskSet.taskset_id == group_id).delete()
session.flush()
session.commit()
@retry
def _forget(self, task_id):
"""Forget about result."""
session = self.ResultSession()
with session_cleanup(session):
session.query(Task).filter(Task.task_id == task_id).delete()
session.commit()
def cleanup(self):
"""Delete expired metadata."""
session = self.ResultSession()
expires = self.expires
now = self.app.now()
with session_cleanup(session):
session.query(Task).filter(
Task.date_done < (now - expires)).delete()
session.query(TaskSet).filter(
TaskSet.date_done < (now - expires)).delete()
session.commit()
def __reduce__(self, args=(), kwargs={}):
kwargs.update(
dict(dburi=self.url,
expires=self.expires,
engine_options=self.engine_options))
return super(DatabaseBackend, self).__reduce__(args, kwargs)
@@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.database.models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Database tables for the SQLAlchemy result store backend.
"""
from __future__ import absolute_import
from datetime import datetime
import sqlalchemy as sa
from sqlalchemy.types import PickleType
from celery import states
from .session import ResultModelBase
__all__ = ['Task', 'TaskSet']
class Task(ResultModelBase):
"""Task result/status."""
__tablename__ = 'celery_taskmeta'
__table_args__ = {'sqlite_autoincrement': True}
id = sa.Column(sa.Integer, sa.Sequence('task_id_sequence'),
primary_key=True,
autoincrement=True)
task_id = sa.Column(sa.String(255), unique=True)
status = sa.Column(sa.String(50), default=states.PENDING)
result = sa.Column(PickleType, nullable=True)
date_done = sa.Column(sa.DateTime, default=datetime.utcnow,
onupdate=datetime.utcnow, nullable=True)
traceback = sa.Column(sa.Text, nullable=True)
def __init__(self, task_id):
self.task_id = task_id
def to_dict(self):
return {'task_id': self.task_id,
'status': self.status,
'result': self.result,
'traceback': self.traceback,
'date_done': self.date_done}
def __repr__(self):
return '<Task {0.task_id} state: {0.status}>'.format(self)
class TaskSet(ResultModelBase):
"""TaskSet result"""
__tablename__ = 'celery_tasksetmeta'
__table_args__ = {'sqlite_autoincrement': True}
id = sa.Column(sa.Integer, sa.Sequence('taskset_id_sequence'),
autoincrement=True, primary_key=True)
taskset_id = sa.Column(sa.String(255), unique=True)
result = sa.Column(PickleType, nullable=True)
date_done = sa.Column(sa.DateTime, default=datetime.utcnow,
nullable=True)
def __init__(self, taskset_id, result):
self.taskset_id = taskset_id
self.result = result
def to_dict(self):
return {'taskset_id': self.taskset_id,
'result': self.result,
'date_done': self.date_done}
def __repr__(self):
return '<TaskSet: {0.taskset_id}>'.format(self)
@@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.database.session
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQLAlchemy sessions.
"""
from __future__ import absolute_import
from billiard.util import register_after_fork
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool
ResultModelBase = declarative_base()
__all__ = ['SessionManager']
class SessionManager(object):
def __init__(self):
self._engines = {}
self._sessions = {}
self.forked = False
self.prepared = False
register_after_fork(self, self._after_fork)
def _after_fork(self,):
self.forked = True
def get_engine(self, dburi, **kwargs):
if self.forked:
try:
return self._engines[dburi]
except KeyError:
engine = self._engines[dburi] = create_engine(dburi, **kwargs)
return engine
else:
kwargs['poolclass'] = NullPool
return create_engine(dburi, **kwargs)
def create_session(self, dburi, short_lived_sessions=False, **kwargs):
engine = self.get_engine(dburi, **kwargs)
if self.forked:
if short_lived_sessions or dburi not in self._sessions:
self._sessions[dburi] = sessionmaker(bind=engine)
return engine, self._sessions[dburi]
else:
return engine, sessionmaker(bind=engine)
def prepare_models(self, engine):
if not self.prepared:
ResultModelBase.metadata.create_all(engine)
self.prepared = True
def session_factory(self, dburi, **kwargs):
engine, session = self.create_session(dburi, **kwargs)
self.prepare_models(engine)
return session()
@@ -1,264 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.mongodb
~~~~~~~~~~~~~~~~~~~~~~~
MongoDB result store backend.
"""
from __future__ import absolute_import
from datetime import datetime
from kombu.syn import detect_environment
from kombu.utils import cached_property
from kombu.utils.url import maybe_sanitize_url
from celery import states
from celery.exceptions import ImproperlyConfigured
from celery.five import items, string_t
from celery.utils.timeutils import maybe_timedelta
from .base import BaseBackend
try:
import pymongo
except ImportError: # pragma: no cover
pymongo = None # noqa
if pymongo:
try:
from bson.binary import Binary
except ImportError: # pragma: no cover
from pymongo.binary import Binary # noqa
else: # pragma: no cover
Binary = None # noqa
__all__ = ['MongoBackend']
class MongoBackend(BaseBackend):
"""MongoDB result backend.
:raises celery.exceptions.ImproperlyConfigured: if
module :mod:`pymongo` is not available.
"""
host = 'localhost'
port = 27017
user = None
password = None
database_name = 'celery'
taskmeta_collection = 'celery_taskmeta'
max_pool_size = 10
options = None
supports_autoexpire = False
_connection = None
def __init__(self, app=None, url=None, **kwargs):
self.options = {}
super(MongoBackend, self).__init__(app, **kwargs)
self.expires = kwargs.get('expires') or maybe_timedelta(
self.app.conf.CELERY_TASK_RESULT_EXPIRES)
if not pymongo:
raise ImproperlyConfigured(
'You need to install the pymongo library to use the '
'MongoDB backend.')
config = self.app.conf.get('CELERY_MONGODB_BACKEND_SETTINGS')
if config is not None:
if not isinstance(config, dict):
raise ImproperlyConfigured(
'MongoDB backend settings should be grouped in a dict')
config = dict(config) # do not modify original
self.host = config.pop('host', self.host)
self.port = int(config.pop('port', self.port))
self.user = config.pop('user', self.user)
self.password = config.pop('password', self.password)
self.database_name = config.pop('database', self.database_name)
self.taskmeta_collection = config.pop(
'taskmeta_collection', self.taskmeta_collection,
)
self.options = dict(config, **config.pop('options', None) or {})
# Set option defaults
for key, value in items(self._prepare_client_options()):
self.options.setdefault(key, value)
self.url = url
if self.url:
# Specifying backend as an URL
self.host = self.url
def _prepare_client_options(self):
if pymongo.version_tuple >= (3, ):
return {'maxPoolSize': self.max_pool_size}
else: # pragma: no cover
options = {
'max_pool_size': self.max_pool_size,
'auto_start_request': False
}
if detect_environment() != 'default':
options['use_greenlets'] = True
return options
def _get_connection(self):
"""Connect to the MongoDB server."""
if self._connection is None:
from pymongo import MongoClient
# The first pymongo.Connection() argument (host) can be
# a list of ['host:port'] elements or a mongodb connection
# URI. If this is the case, don't use self.port
# but let pymongo get the port(s) from the URI instead.
# This enables the use of replica sets and sharding.
# See pymongo.Connection() for more info.
url = self.host
if isinstance(url, string_t) \
and not url.startswith('mongodb://'):
url = 'mongodb://{0}:{1}'.format(url, self.port)
if url == 'mongodb://':
url = url + 'localhost'
self._connection = MongoClient(host=url, **self.options)
return self._connection
def process_cleanup(self):
if self._connection is not None:
# MongoDB connection will be closed automatically when object
# goes out of scope
del(self.collection)
del(self.database)
self._connection = None
def _store_result(self, task_id, result, status,
traceback=None, request=None, **kwargs):
"""Store return value and status of an executed task."""
meta = {'_id': task_id,
'status': status,
'result': Binary(self.encode(result)),
'date_done': datetime.utcnow(),
'traceback': Binary(self.encode(traceback)),
'children': Binary(self.encode(
self.current_task_children(request),
))}
self.collection.save(meta)
return result
def _get_task_meta_for(self, task_id):
"""Get task metadata for a task by id."""
obj = self.collection.find_one({'_id': task_id})
if not obj:
return {'status': states.PENDING, 'result': None}
meta = {
'task_id': obj['_id'],
'status': obj['status'],
'result': self.decode(obj['result']),
'date_done': obj['date_done'],
'traceback': self.decode(obj['traceback']),
'children': self.decode(obj['children']),
}
return meta
def _save_group(self, group_id, result):
"""Save the group result."""
meta = {'_id': group_id,
'result': Binary(self.encode(result)),
'date_done': datetime.utcnow()}
self.collection.save(meta)
return result
def _restore_group(self, group_id):
"""Get the result for a group by id."""
obj = self.collection.find_one({'_id': group_id})
if not obj:
return
meta = {
'task_id': obj['_id'],
'result': self.decode(obj['result']),
'date_done': obj['date_done'],
}
return meta
def _delete_group(self, group_id):
"""Delete a group by id."""
self.collection.remove({'_id': group_id})
def _forget(self, task_id):
"""Remove result from MongoDB.
:raises celery.exceptions.OperationsError:
if the task_id could not be removed.
"""
# By using safe=True, this will wait until it receives a response from
# the server. Likewise, it will raise an OperationsError if the
# response was unable to be completed.
self.collection.remove({'_id': task_id})
def cleanup(self):
"""Delete expired metadata."""
self.collection.remove(
{'date_done': {'$lt': self.app.now() - self.expires}},
)
def __reduce__(self, args=(), kwargs={}):
return super(MongoBackend, self).__reduce__(
args, dict(kwargs, expires=self.expires, url=self.url),
)
def _get_database(self):
conn = self._get_connection()
db = conn[self.database_name]
if self.user and self.password:
if not db.authenticate(self.user,
self.password):
raise ImproperlyConfigured(
'Invalid MongoDB username or password.')
return db
@cached_property
def database(self):
"""Get database from MongoDB connection and perform authentication
if necessary."""
return self._get_database()
@cached_property
def collection(self):
"""Get the metadata task collection."""
collection = self.database[self.taskmeta_collection]
# Ensure an index on date_done is there, if not process the index
# in the background. Once completed cleanup will be much faster
collection.ensure_index('date_done', background='true')
return collection
def as_uri(self, include_password=False):
"""Return the backend as an URI.
:keyword include_password: Censor passwords.
"""
if not self.url:
return 'mongodb://'
if include_password:
return self.url
if ',' not in self.url:
return maybe_sanitize_url(self.url)
uri1, remainder = self.url.split(',', 1)
return ','.join([maybe_sanitize_url(uri1), remainder])
@@ -1,295 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.redis
~~~~~~~~~~~~~~~~~~~~~
Redis result store backend.
"""
from __future__ import absolute_import
from functools import partial
from kombu.utils import cached_property, retry_over_time
from kombu.utils.url import _parse_url
from celery import states
from celery.canvas import maybe_signature
from celery.exceptions import ChordError, ImproperlyConfigured
from celery.five import string_t
from celery.utils import deprecated_property, strtobool
from celery.utils.functional import dictfilter
from celery.utils.log import get_logger
from celery.utils.timeutils import humanize_seconds
from .base import KeyValueStoreBackend
try:
import redis
from redis.exceptions import ConnectionError
from kombu.transport.redis import get_redis_error_classes
except ImportError: # pragma: no cover
redis = None # noqa
ConnectionError = None # noqa
get_redis_error_classes = None # noqa
__all__ = ['RedisBackend']
REDIS_MISSING = """\
You need to install the redis library in order to use \
the Redis result store backend."""
logger = get_logger(__name__)
error = logger.error
class RedisBackend(KeyValueStoreBackend):
"""Redis task result store."""
#: redis-py client module.
redis = redis
#: Maximium number of connections in the pool.
max_connections = None
supports_autoexpire = True
supports_native_join = True
implements_incr = True
def __init__(self, host=None, port=None, db=None, password=None,
expires=None, max_connections=None, url=None,
connection_pool=None, new_join=False, **kwargs):
super(RedisBackend, self).__init__(**kwargs)
conf = self.app.conf
if self.redis is None:
raise ImproperlyConfigured(REDIS_MISSING)
self._client_capabilities = self._detect_client_capabilities()
# For compatibility with the old REDIS_* configuration keys.
def _get(key):
for prefix in 'CELERY_REDIS_{0}', 'REDIS_{0}':
try:
return conf[prefix.format(key)]
except KeyError:
pass
if host and '://' in host:
url = host
host = None
self.max_connections = (
max_connections or _get('MAX_CONNECTIONS') or self.max_connections
)
self._ConnectionPool = connection_pool
self.connparams = {
'host': _get('HOST') or 'localhost',
'port': _get('PORT') or 6379,
'db': _get('DB') or 0,
'password': _get('PASSWORD'),
'max_connections': self.max_connections,
}
if url:
self.connparams = self._params_from_url(url, self.connparams)
self.url = url
self.expires = self.prepare_expires(expires, type=int)
try:
new_join = strtobool(self.connparams.pop('new_join'))
except KeyError:
pass
if new_join:
self.apply_chord = self._new_chord_apply
self.on_chord_part_return = self._new_chord_return
self.connection_errors, self.channel_errors = (
get_redis_error_classes() if get_redis_error_classes
else ((), ()))
def _params_from_url(self, url, defaults):
scheme, host, port, user, password, path, query = _parse_url(url)
connparams = dict(
defaults, **dictfilter({
'host': host, 'port': port, 'password': password,
'db': query.pop('virtual_host', None)})
)
if scheme == 'socket':
# use 'path' as path to the socket… in this case
# the database number should be given in 'query'
connparams.update({
'connection_class': self.redis.UnixDomainSocketConnection,
'path': '/' + path,
})
# host+port are invalid options when using this connection type.
connparams.pop('host', None)
connparams.pop('port', None)
else:
connparams['db'] = path
# db may be string and start with / like in kombu.
db = connparams.get('db') or 0
db = db.strip('/') if isinstance(db, string_t) else db
connparams['db'] = int(db)
# Query parameters override other parameters
connparams.update(query)
return connparams
def get(self, key):
return self.client.get(key)
def mget(self, keys):
return self.client.mget(keys)
def ensure(self, fun, args, **policy):
retry_policy = dict(self.retry_policy, **policy)
max_retries = retry_policy.get('max_retries')
return retry_over_time(
fun, self.connection_errors, args, {},
partial(self.on_connection_error, max_retries),
**retry_policy
)
def on_connection_error(self, max_retries, exc, intervals, retries):
tts = next(intervals)
error('Connection to Redis lost: Retry (%s/%s) %s.',
retries, max_retries or 'Inf',
humanize_seconds(tts, 'in '))
return tts
def set(self, key, value, **retry_policy):
return self.ensure(self._set, (key, value), **retry_policy)
def _set(self, key, value):
with self.client.pipeline() as pipe:
if self.expires:
pipe.setex(key, value, self.expires)
else:
pipe.set(key, value)
pipe.publish(key, value)
pipe.execute()
def delete(self, key):
self.client.delete(key)
def incr(self, key):
return self.client.incr(key)
def expire(self, key, value):
return self.client.expire(key, value)
def _unpack_chord_result(self, tup, decode,
EXCEPTION_STATES=states.EXCEPTION_STATES,
PROPAGATE_STATES=states.PROPAGATE_STATES):
_, tid, state, retval = decode(tup)
if state in EXCEPTION_STATES:
retval = self.exception_to_python(retval)
if state in PROPAGATE_STATES:
raise ChordError('Dependency {0} raised {1!r}'.format(tid, retval))
return retval
def _new_chord_apply(self, header, partial_args, group_id, body,
result=None, **options):
# avoids saving the group in the redis db.
return header(*partial_args, task_id=group_id)
def _new_chord_return(self, task, state, result, propagate=None,
PROPAGATE_STATES=states.PROPAGATE_STATES):
app = self.app
if propagate is None:
propagate = self.app.conf.CELERY_CHORD_PROPAGATES
request = task.request
tid, gid = request.id, request.group
if not gid or not tid:
return
client = self.client
jkey = self.get_key_for_group(gid, '.j')
result = self.encode_result(result, state)
with client.pipeline() as pipe:
_, readycount, _ = pipe \
.rpush(jkey, self.encode([1, tid, state, result])) \
.llen(jkey) \
.expire(jkey, 86400) \
.execute()
try:
callback = maybe_signature(request.chord, app=app)
total = callback['chord_size']
if readycount == total:
decode, unpack = self.decode, self._unpack_chord_result
with client.pipeline() as pipe:
resl, _, = pipe \
.lrange(jkey, 0, total) \
.delete(jkey) \
.execute()
try:
callback.delay([unpack(tup, decode) for tup in resl])
except Exception as exc:
error('Chord callback for %r raised: %r',
request.group, exc, exc_info=1)
return self.chord_error_from_stack(
callback,
ChordError('Callback error: {0!r}'.format(exc)),
)
except ChordError as exc:
error('Chord %r raised: %r', request.group, exc, exc_info=1)
return self.chord_error_from_stack(callback, exc)
except Exception as exc:
error('Chord %r raised: %r', request.group, exc, exc_info=1)
return self.chord_error_from_stack(
callback, ChordError('Join error: {0!r}'.format(exc)),
)
def _detect_client_capabilities(self, socket_connect_timeout=False):
if self.redis.VERSION < (2, 4, 4):
raise ImproperlyConfigured(
'Redis backend requires redis-py versions 2.4.4 or later. '
'You have {0.__version__}'.format(redis))
if self.redis.VERSION >= (2, 10):
socket_connect_timeout = True
return {'socket_connect_timeout': socket_connect_timeout}
def _create_client(self, socket_timeout=None, socket_connect_timeout=None,
**params):
return self._new_redis_client(
socket_timeout=socket_timeout and float(socket_timeout),
socket_connect_timeout=socket_connect_timeout and float(
socket_connect_timeout), **params
)
def _new_redis_client(self, **params):
if not self._client_capabilities['socket_connect_timeout']:
params.pop('socket_connect_timeout', None)
return self.redis.Redis(connection_pool=self.ConnectionPool(**params))
@property
def ConnectionPool(self):
if self._ConnectionPool is None:
self._ConnectionPool = self.redis.ConnectionPool
return self._ConnectionPool
@cached_property
def client(self):
return self._create_client(**self.connparams)
def __reduce__(self, args=(), kwargs={}):
return super(RedisBackend, self).__reduce__(
(self.url, ), {'expires': self.expires},
)
@deprecated_property(3.2, 3.3)
def host(self):
return self.connparams['host']
@deprecated_property(3.2, 3.3)
def port(self):
return self.connparams['port']
@deprecated_property(3.2, 3.3)
def db(self):
return self.connparams['db']
@deprecated_property(3.2, 3.3)
def password(self):
return self.connparams['password']
@@ -1,67 +0,0 @@
# -*- coding: utf-8 -*-
"""
celery.backends.rpc
~~~~~~~~~~~~~~~~~~~
RPC-style result backend, using reply-to and one queue per client.
"""
from __future__ import absolute_import
from kombu import Consumer, Exchange
from kombu.common import maybe_declare
from kombu.utils import cached_property
from celery import current_task
from celery.backends import amqp
__all__ = ['RPCBackend']
class RPCBackend(amqp.AMQPBackend):
persistent = False
class Consumer(Consumer):
auto_declare = False
def _create_exchange(self, name, type='direct', delivery_mode=2):
# uses direct to queue routing (anon exchange).
return Exchange(None)
def on_task_call(self, producer, task_id):
maybe_declare(self.binding(producer.channel), retry=True)
def _create_binding(self, task_id):
return self.binding
def _many_bindings(self, ids):
return [self.binding]
def rkey(self, task_id):
return task_id
def destination_for(self, task_id, request):
# Request is a new argument for backends, so must still support
# old code that rely on current_task
try:
request = request or current_task.request
except AttributeError:
raise RuntimeError(
'RPC backend missing task request for {0!r}'.format(task_id),
)
return request.reply_to, request.correlation_id or task_id
def on_reply_declare(self, task_id):
pass
def as_uri(self, include_password=True):
return 'rpc://'
@property
def binding(self):
return self.Queue(self.oid, self.exchange, self.oid,
durable=False, auto_delete=False)
@cached_property
def oid(self):
return self.app.oid
Loaded 100 of 647 files, more files were not shown because too many files have changed in this diff. Show more