Compare commits
16
Commits
newsletter
...
02adc8fe6d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02adc8fe6d | ||
|
|
ac66d766c5 | ||
|
|
535857581f | ||
|
|
7362469204 | ||
|
|
02551b24b5 | ||
|
|
6c5bcb0131 | ||
|
|
acb8668991 | ||
|
|
a577e47acc | ||
|
|
3b9f4c33f2 | ||
|
|
62c84570cc | ||
|
|
d773cba908 | ||
|
|
38a25437b6 | ||
|
|
292e9eda2a | ||
|
|
65d167a8b8 | ||
|
|
92fda4831a | ||
|
|
806718824c |
No files matched your search
+46
@@ -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/
|
||||
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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']
|
||||
@@ -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
@@ -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
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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
@@ -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.
+36
@@ -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
|
||||
BIN
Binary file not shown.
@@ -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}
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
@@ -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'
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start Gunicorn processes
|
||||
python3 manage.py runserver
|
||||
@@ -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())
|
||||
Executable
BIN
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
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+3
-4
@@ -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'
|
||||
+1185
-1179
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -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
|
||||
|
||||
File renamed without changes.
@@ -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"}
|
||||
File renamed without changes.
@@ -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
Reference in new issue
Block a user