Formatting
This commit is contained in:
		
							parent
							
								
									4a385bde6b
								
							
						
					
					
						commit
						befc7ad2cd
					
				
					 28 changed files with 253 additions and 311 deletions
				
			
		| 
						 | 
				
			
			@ -10,6 +10,4 @@ For your project, ignore this file and add
 | 
			
		|||
to your site's urlconf.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('lists/', include('todo.urls')),
 | 
			
		||||
]
 | 
			
		||||
urlpatterns = [path("lists/", include("todo.urls"))]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,11 +2,7 @@ import os
 | 
			
		|||
 | 
			
		||||
DEBUG = (True,)
 | 
			
		||||
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
 | 
			
		||||
DATABASES = {
 | 
			
		||||
    "default": {
 | 
			
		||||
        "ENGINE": "django.db.backends.sqlite3"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
 | 
			
		||||
 | 
			
		||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -65,28 +61,12 @@ TEMPLATES = [
 | 
			
		|||
]
 | 
			
		||||
 | 
			
		||||
LOGGING = {
 | 
			
		||||
    'version': 1,
 | 
			
		||||
    'disable_existing_loggers': False,
 | 
			
		||||
    'handlers': {
 | 
			
		||||
        'console': {
 | 
			
		||||
            'class': 'logging.StreamHandler',
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    'loggers': {
 | 
			
		||||
        '': {
 | 
			
		||||
            'handlers': ['console'],
 | 
			
		||||
            'level': 'DEBUG',
 | 
			
		||||
            'propagate': True,
 | 
			
		||||
        },
 | 
			
		||||
        'django': {
 | 
			
		||||
            'handlers': ['console'],
 | 
			
		||||
            'level': 'WARNING',
 | 
			
		||||
            'propagate': True,
 | 
			
		||||
        },
 | 
			
		||||
        'django.request': {
 | 
			
		||||
            'handlers': ['console'],
 | 
			
		||||
            'level': 'DEBUG',
 | 
			
		||||
            'propagate': True,
 | 
			
		||||
        },
 | 
			
		||||
    "version": 1,
 | 
			
		||||
    "disable_existing_loggers": False,
 | 
			
		||||
    "handlers": {"console": {"class": "logging.StreamHandler"}},
 | 
			
		||||
    "loggers": {
 | 
			
		||||
        "": {"handlers": ["console"], "level": "DEBUG", "propagate": True},
 | 
			
		||||
        "django": {"handlers": ["console"], "level": "WARNING", "propagate": True},
 | 
			
		||||
        "django.request": {"handlers": ["console"], "level": "DEBUG", "propagate": True},
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
"""
 | 
			
		||||
A multi-user, multi-group task management and assignment system for Django.
 | 
			
		||||
"""
 | 
			
		||||
__version__ = '2.4.6'
 | 
			
		||||
__version__ = "2.4.6"
 | 
			
		||||
 | 
			
		||||
__author__ = 'Scot Hacker'
 | 
			
		||||
__email__ = 'shacker@birdhouse.org'
 | 
			
		||||
__author__ = "Scot Hacker"
 | 
			
		||||
__email__ = "shacker@birdhouse.org"
 | 
			
		||||
 | 
			
		||||
__url__ = 'https://github.com/shacker/django-todo'
 | 
			
		||||
__license__ = 'BSD License'
 | 
			
		||||
__url__ = "https://github.com/shacker/django-todo"
 | 
			
		||||
__license__ = "BSD License"
 | 
			
		||||
 | 
			
		||||
from . import check
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,9 +11,7 @@ def dal_check(app_configs, **kwargs):
 | 
			
		|||
        return []
 | 
			
		||||
 | 
			
		||||
    errors = []
 | 
			
		||||
    missing_apps = {'dal', 'dal_select2'} - set(settings.INSTALLED_APPS)
 | 
			
		||||
    missing_apps = {"dal", "dal_select2"} - set(settings.INSTALLED_APPS)
 | 
			
		||||
    for missing_app in missing_apps:
 | 
			
		||||
        errors.append(
 | 
			
		||||
            Error('{} needs to be in INSTALLED_APPS'.format(missing_app))
 | 
			
		||||
        )
 | 
			
		||||
        errors.append(Error("{} needs to be in INSTALLED_APPS".format(missing_app)))
 | 
			
		||||
    return errors
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,5 +11,6 @@ except ImportError:
 | 
			
		|||
HAS_TASK_MERGE = False
 | 
			
		||||
if HAS_AUTOCOMPLETE:
 | 
			
		||||
    import dal.autocomplete
 | 
			
		||||
    if getattr(dal.autocomplete, 'Select2QuerySetView', None) is not None:
 | 
			
		||||
 | 
			
		||||
    if getattr(dal.autocomplete, "Select2QuerySetView", None) is not None:
 | 
			
		||||
        HAS_TASK_MERGE = True
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,9 @@
 | 
			
		|||
import importlib
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _declare_backend(backend_path):
 | 
			
		||||
    backend_path = backend_path.split('.')
 | 
			
		||||
    backend_module_name = '.'.join(backend_path[:-1])
 | 
			
		||||
    backend_path = backend_path.split(".")
 | 
			
		||||
    backend_module_name = ".".join(backend_path[:-1])
 | 
			
		||||
    class_name = backend_path[-1]
 | 
			
		||||
 | 
			
		||||
    def backend(*args, headers={}, from_address=None, **kwargs):
 | 
			
		||||
| 
						 | 
				
			
			@ -17,9 +18,10 @@ def _declare_backend(backend_path):
 | 
			
		|||
        _backend.from_address = from_address
 | 
			
		||||
        _backend.headers = headers
 | 
			
		||||
        return _backend
 | 
			
		||||
 | 
			
		||||
    return backend
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
smtp_backend = _declare_backend('django.core.mail.backends.smtp.EmailBackend')
 | 
			
		||||
console_backend = _declare_backend('django.core.mail.backends.console.EmailBackend')
 | 
			
		||||
locmem_backend = _declare_backend('django.core.mail.backends.locmem.EmailBackend')
 | 
			
		||||
smtp_backend = _declare_backend("django.core.mail.backends.smtp.EmailBackend")
 | 
			
		||||
console_backend = _declare_backend("django.core.mail.backends.console.EmailBackend")
 | 
			
		||||
locmem_backend = _declare_backend("django.core.mail.backends.locmem.EmailBackend")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,14 +70,12 @@ def imap_producer(
 | 
			
		|||
                    try:
 | 
			
		||||
                        yield message
 | 
			
		||||
                    except Exception:
 | 
			
		||||
                        logger.exception(
 | 
			
		||||
                            f"something went wrong while processing {message_uid}"
 | 
			
		||||
                        )
 | 
			
		||||
                        logger.exception(f"something went wrong while processing {message_uid}")
 | 
			
		||||
                        raise
 | 
			
		||||
 | 
			
		||||
                    if not preserve:
 | 
			
		||||
                        # tag the message for deletion
 | 
			
		||||
                        conn.store(message_uid, '+FLAGS', '\\Deleted')
 | 
			
		||||
                        conn.store(message_uid, "+FLAGS", "\\Deleted")
 | 
			
		||||
                else:
 | 
			
		||||
                    logger.debug("did not receive any message")
 | 
			
		||||
            finally:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ def gen_title(tc=True):
 | 
			
		|||
    # faker doesn't provide a way to generate headlines in Title Case, without periods, so make our own.
 | 
			
		||||
    # With arg `tc=True`, Title Cases The Generated Text
 | 
			
		||||
    fake = Faker()
 | 
			
		||||
    thestr = fake.text(max_nb_chars=32).rstrip('.')
 | 
			
		||||
    thestr = fake.text(max_nb_chars=32).rstrip(".")
 | 
			
		||||
    if tc:
 | 
			
		||||
        thestr = titlecase(thestr)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ def gen_content():
 | 
			
		|||
    # faker provides paragraphs as a list; convert with linebreaks
 | 
			
		||||
    fake = Faker()
 | 
			
		||||
    grafs = fake.paragraphs()
 | 
			
		||||
    thestr = ''
 | 
			
		||||
    thestr = ""
 | 
			
		||||
    for g in grafs:
 | 
			
		||||
        thestr += "{}\n\n".format(g)
 | 
			
		||||
    return thestr
 | 
			
		||||
| 
						 | 
				
			
			@ -43,11 +43,12 @@ class Command(BaseCommand):
 | 
			
		|||
            "-d",
 | 
			
		||||
            "--delete",
 | 
			
		||||
            help="Wipe out existing content before generating new.",
 | 
			
		||||
            action="store_true")
 | 
			
		||||
            action="store_true",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def handle(self, *args, **options):
 | 
			
		||||
 | 
			
		||||
        if options.get('delete'):
 | 
			
		||||
        if options.get("delete"):
 | 
			
		||||
            # Wipe out previous contents? Cascade deletes the Tasks from the TaskLists.
 | 
			
		||||
            TaskList.objects.all().delete()
 | 
			
		||||
            print("Content from previous run deleted.")
 | 
			
		||||
| 
						 | 
				
			
			@ -56,11 +57,11 @@ class Command(BaseCommand):
 | 
			
		|||
        fake = Faker()  # Use to create user's names
 | 
			
		||||
 | 
			
		||||
        # Create users and groups, add different users to different groups. Staff user is in both groups.
 | 
			
		||||
        sd_group, created = Group.objects.get_or_create(name='Scuba Divers')
 | 
			
		||||
        bw_group, created = Group.objects.get_or_create(name='Basket Weavers')
 | 
			
		||||
        sd_group, created = Group.objects.get_or_create(name="Scuba Divers")
 | 
			
		||||
        bw_group, created = Group.objects.get_or_create(name="Basket Weavers")
 | 
			
		||||
 | 
			
		||||
        # Put user1 and user2 in one group, user3 and user4 in another
 | 
			
		||||
        usernames = ['user1', 'user2', 'user3', 'user4', 'staffer']
 | 
			
		||||
        usernames = ["user1", "user2", "user3", "user4", "staffer"]
 | 
			
		||||
        for username in usernames:
 | 
			
		||||
            if get_user_model().objects.filter(username=username).exists():
 | 
			
		||||
                user = get_user_model().objects.get(username=username)
 | 
			
		||||
| 
						 | 
				
			
			@ -70,15 +71,16 @@ class Command(BaseCommand):
 | 
			
		|||
                    first_name=fake.first_name(),
 | 
			
		||||
                    last_name=fake.last_name(),
 | 
			
		||||
                    email="{}@example.com".format(username),
 | 
			
		||||
                    password="todo")
 | 
			
		||||
                    password="todo",
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            if username in ['user1', 'user2']:
 | 
			
		||||
            if username in ["user1", "user2"]:
 | 
			
		||||
                user.groups.add(bw_group)
 | 
			
		||||
 | 
			
		||||
            if username in ['user3', 'user4']:
 | 
			
		||||
            if username in ["user3", "user4"]:
 | 
			
		||||
                user.groups.add(sd_group)
 | 
			
		||||
 | 
			
		||||
            if username == 'staffer':
 | 
			
		||||
            if username == "staffer":
 | 
			
		||||
                user.is_staff = True
 | 
			
		||||
                user.first_name = fake.first_name()
 | 
			
		||||
                user.last_name = fake.last_name()
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +93,9 @@ class Command(BaseCommand):
 | 
			
		|||
        TaskListFactory.create_batch(5, group=sd_group)
 | 
			
		||||
        TaskListFactory.create(name="Public Tickets", slug="tickets", group=bw_group)
 | 
			
		||||
 | 
			
		||||
        print("For each of two groups, created fake tasks in each of {} fake lists.".format(num_lists))
 | 
			
		||||
        print(
 | 
			
		||||
            "For each of two groups, created fake tasks in each of {} fake lists.".format(num_lists)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TaskListFactory(factory.django.DjangoModelFactory):
 | 
			
		||||
| 
						 | 
				
			
			@ -120,9 +124,11 @@ class TaskFactory(factory.django.DjangoModelFactory):
 | 
			
		|||
    task_list = None  # Pass this in
 | 
			
		||||
    note = factory.LazyAttribute(lambda o: gen_content())
 | 
			
		||||
    priority = factory.LazyAttribute(lambda o: random.randint(1, 100))
 | 
			
		||||
    completed = factory.Faker('boolean', chance_of_getting_true=30)
 | 
			
		||||
    created_by = factory.LazyAttribute(lambda o: get_user_model().objects.get(username='staffer'))  # Randomized in post
 | 
			
		||||
    created_date = factory.Faker('date_this_year')
 | 
			
		||||
    completed = factory.Faker("boolean", chance_of_getting_true=30)
 | 
			
		||||
    created_by = factory.LazyAttribute(
 | 
			
		||||
        lambda o: get_user_model().objects.get(username="staffer")
 | 
			
		||||
    )  # Randomized in post
 | 
			
		||||
    created_date = factory.Faker("date_this_year")
 | 
			
		||||
 | 
			
		||||
    @factory.post_generation
 | 
			
		||||
    def add_details(self, build, extracted, **kwargs):
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +136,7 @@ class TaskFactory(factory.django.DjangoModelFactory):
 | 
			
		|||
        fake = Faker()  # Use to create user's names
 | 
			
		||||
        taskgroup = self.task_list.group
 | 
			
		||||
 | 
			
		||||
        self.created_by = taskgroup.user_set.all().order_by('?').first()
 | 
			
		||||
        self.created_by = taskgroup.user_set.all().order_by("?").first()
 | 
			
		||||
 | 
			
		||||
        if self.completed:
 | 
			
		||||
            self.completed_date = fake.date_this_year()
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +147,6 @@ class TaskFactory(factory.django.DjangoModelFactory):
 | 
			
		|||
 | 
			
		||||
        # 1/3 of generated tasks are assigned to someone in this tasks's group
 | 
			
		||||
        if random.randint(1, 3) == 1:
 | 
			
		||||
            self.assigned_to = taskgroup.user_set.all().order_by('?').first()
 | 
			
		||||
            self.assigned_to = taskgroup.user_set.all().order_by("?").first()
 | 
			
		||||
 | 
			
		||||
        self.save()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,10 +26,7 @@ class Command(BaseCommand):
 | 
			
		|||
        worker_name = options["worker_name"]
 | 
			
		||||
        tracker = settings.TODO_MAIL_TRACKERS.get(worker_name, None)
 | 
			
		||||
        if tracker is None:
 | 
			
		||||
            logger.error(
 | 
			
		||||
                "couldn't find configuration for %r in TODO_MAIL_TRACKERS",
 | 
			
		||||
                worker_name
 | 
			
		||||
            )
 | 
			
		||||
            logger.error("couldn't find configuration for %r in TODO_MAIL_TRACKERS", worker_name)
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
 | 
			
		||||
        # set the default socket timeout (imaplib doesn't enable configuring it)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,70 +9,93 @@ from django.conf import settings
 | 
			
		|||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('auth', '0001_initial'),
 | 
			
		||||
        ("auth", "0001_initial"),
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Comment',
 | 
			
		||||
            name="Comment",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
 | 
			
		||||
                ('date', models.DateTimeField(default=datetime.datetime.now)),
 | 
			
		||||
                ('body', models.TextField(blank=True)),
 | 
			
		||||
                ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        verbose_name="ID", serialize=False, auto_created=True, primary_key=True
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("date", models.DateTimeField(default=datetime.datetime.now)),
 | 
			
		||||
                ("body", models.TextField(blank=True)),
 | 
			
		||||
                (
 | 
			
		||||
                    "author",
 | 
			
		||||
                    models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
            },
 | 
			
		||||
            options={},
 | 
			
		||||
            bases=(models.Model,),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Item',
 | 
			
		||||
            name="Item",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
 | 
			
		||||
                ('title', models.CharField(max_length=140)),
 | 
			
		||||
                ('created_date', models.DateField(auto_now=True, auto_now_add=True)),
 | 
			
		||||
                ('due_date', models.DateField(null=True, blank=True)),
 | 
			
		||||
                ('completed', models.BooleanField(default=None)),
 | 
			
		||||
                ('completed_date', models.DateField(null=True, blank=True)),
 | 
			
		||||
                ('note', models.TextField(null=True, blank=True)),
 | 
			
		||||
                ('priority', models.PositiveIntegerField(max_length=3)),
 | 
			
		||||
                ('assigned_to', models.ForeignKey(related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL,  on_delete=models.CASCADE)),
 | 
			
		||||
                ('created_by', models.ForeignKey(related_name='todo_created_by', to=settings.AUTH_USER_MODEL,  on_delete=models.CASCADE)),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        verbose_name="ID", serialize=False, auto_created=True, primary_key=True
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("title", models.CharField(max_length=140)),
 | 
			
		||||
                ("created_date", models.DateField(auto_now=True, auto_now_add=True)),
 | 
			
		||||
                ("due_date", models.DateField(null=True, blank=True)),
 | 
			
		||||
                ("completed", models.BooleanField(default=None)),
 | 
			
		||||
                ("completed_date", models.DateField(null=True, blank=True)),
 | 
			
		||||
                ("note", models.TextField(null=True, blank=True)),
 | 
			
		||||
                ("priority", models.PositiveIntegerField(max_length=3)),
 | 
			
		||||
                (
 | 
			
		||||
                    "assigned_to",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        related_name="todo_assigned_to",
 | 
			
		||||
                        to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                        on_delete=models.CASCADE,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "created_by",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        related_name="todo_created_by",
 | 
			
		||||
                        to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                        on_delete=models.CASCADE,
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'ordering': ['priority'],
 | 
			
		||||
            },
 | 
			
		||||
            options={"ordering": ["priority"]},
 | 
			
		||||
            bases=(models.Model,),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='List',
 | 
			
		||||
            name="List",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
 | 
			
		||||
                ('name', models.CharField(max_length=60)),
 | 
			
		||||
                ('slug', models.SlugField(max_length=60, editable=False)),
 | 
			
		||||
                ('group', models.ForeignKey(to='auth.Group', on_delete=models.CASCADE)),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        verbose_name="ID", serialize=False, auto_created=True, primary_key=True
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.CharField(max_length=60)),
 | 
			
		||||
                ("slug", models.SlugField(max_length=60, editable=False)),
 | 
			
		||||
                ("group", models.ForeignKey(to="auth.Group", on_delete=models.CASCADE)),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'ordering': ['name'],
 | 
			
		||||
                'verbose_name_plural': 'Lists',
 | 
			
		||||
            },
 | 
			
		||||
            options={"ordering": ["name"], "verbose_name_plural": "Lists"},
 | 
			
		||||
            bases=(models.Model,),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterUniqueTogether(
 | 
			
		||||
            name='list',
 | 
			
		||||
            unique_together=set([('group', 'slug')]),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterUniqueTogether(name="list", unique_together=set([("group", "slug")])),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='list',
 | 
			
		||||
            field=models.ForeignKey(to='todo.List', on_delete=models.CASCADE),
 | 
			
		||||
            model_name="item",
 | 
			
		||||
            name="list",
 | 
			
		||||
            field=models.ForeignKey(to="todo.List", on_delete=models.CASCADE),
 | 
			
		||||
            preserve_default=True,
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='comment',
 | 
			
		||||
            name='task',
 | 
			
		||||
            field=models.ForeignKey(to='todo.Item', on_delete=models.CASCADE),
 | 
			
		||||
            model_name="comment",
 | 
			
		||||
            name="task",
 | 
			
		||||
            field=models.ForeignKey(to="todo.Item", on_delete=models.CASCADE),
 | 
			
		||||
            preserve_default=True,
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,19 +6,13 @@ from django.db import models, migrations
 | 
			
		|||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('todo', '0001_initial'),
 | 
			
		||||
    ]
 | 
			
		||||
    dependencies = [("todo", "0001_initial")]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='created_date',
 | 
			
		||||
            field=models.DateField(auto_now=True),
 | 
			
		||||
            model_name="item", name="created_date", field=models.DateField(auto_now=True)
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='priority',
 | 
			
		||||
            field=models.PositiveIntegerField(),
 | 
			
		||||
            model_name="item", name="priority", field=models.PositiveIntegerField()
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,14 +9,18 @@ import django.db.models.deletion
 | 
			
		|||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('todo', '0002_auto_20150614_2339'),
 | 
			
		||||
    ]
 | 
			
		||||
    dependencies = [("todo", "0002_auto_20150614_2339")]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='assigned_to',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL),
 | 
			
		||||
        ),
 | 
			
		||||
            model_name="item",
 | 
			
		||||
            name="assigned_to",
 | 
			
		||||
            field=models.ForeignKey(
 | 
			
		||||
                blank=True,
 | 
			
		||||
                null=True,
 | 
			
		||||
                on_delete=django.db.models.deletion.CASCADE,
 | 
			
		||||
                related_name="todo_assigned_to",
 | 
			
		||||
                to=settings.AUTH_USER_MODEL,
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,46 +7,39 @@ import django.db.models.deletion
 | 
			
		|||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('auth', '0009_alter_user_last_name_max_length'),
 | 
			
		||||
        ('todo', '0003_assignee_optional'),
 | 
			
		||||
        ("auth", "0009_alter_user_last_name_max_length"),
 | 
			
		||||
        ("todo", "0003_assignee_optional"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='TaskList',
 | 
			
		||||
            name="TaskList",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('name', models.CharField(max_length=60)),
 | 
			
		||||
                ('slug', models.SlugField(default='')),
 | 
			
		||||
                ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("name", models.CharField(max_length=60)),
 | 
			
		||||
                ("slug", models.SlugField(default="")),
 | 
			
		||||
                (
 | 
			
		||||
                    "group",
 | 
			
		||||
                    models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="auth.Group"),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name_plural': 'Lists',
 | 
			
		||||
                'ordering': ['name'],
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterUniqueTogether(
 | 
			
		||||
            name='list',
 | 
			
		||||
            unique_together=set(),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='list',
 | 
			
		||||
            name='group',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.RemoveField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='list',
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='List',
 | 
			
		||||
            options={"verbose_name_plural": "Lists", "ordering": ["name"]},
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterUniqueTogether(name="list", unique_together=set()),
 | 
			
		||||
        migrations.RemoveField(model_name="list", name="group"),
 | 
			
		||||
        migrations.RemoveField(model_name="item", name="list"),
 | 
			
		||||
        migrations.DeleteModel(name="List"),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='task_list',
 | 
			
		||||
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='todo.TaskList'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterUniqueTogether(
 | 
			
		||||
            name='tasklist',
 | 
			
		||||
            unique_together={('group', 'slug')},
 | 
			
		||||
            model_name="item",
 | 
			
		||||
            name="task_list",
 | 
			
		||||
            field=models.ForeignKey(
 | 
			
		||||
                null=True, on_delete=django.db.models.deletion.CASCADE, to="todo.TaskList"
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterUniqueTogether(name="tasklist", unique_together={("group", "slug")}),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,18 +5,13 @@ from django.db import migrations, models
 | 
			
		|||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('todo', '0004_rename_list_tasklist'),
 | 
			
		||||
    ]
 | 
			
		||||
    dependencies = [("todo", "0004_rename_list_tasklist")]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name='tasklist',
 | 
			
		||||
            options={'ordering': ['name'], 'verbose_name_plural': 'Task Lists'},
 | 
			
		||||
            name="tasklist", options={"ordering": ["name"], "verbose_name_plural": "Task Lists"}
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='item',
 | 
			
		||||
            name='completed',
 | 
			
		||||
            field=models.BooleanField(default=False),
 | 
			
		||||
            model_name="item", name="completed", field=models.BooleanField(default=False)
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,12 +8,7 @@ class Migration(migrations.Migration):
 | 
			
		|||
    atomic = False
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
        ('todo', '0005_auto_20180212_2325'),
 | 
			
		||||
        ("todo", "0005_auto_20180212_2325"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.RenameModel(
 | 
			
		||||
            old_name='Item',
 | 
			
		||||
            new_name='Task',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
    operations = [migrations.RenameModel(old_name="Item", new_name="Task")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,14 +6,12 @@ import django.utils.timezone
 | 
			
		|||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('todo', '0006_rename_item_model'),
 | 
			
		||||
    ]
 | 
			
		||||
    dependencies = [("todo", "0006_rename_item_model")]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='task',
 | 
			
		||||
            name='created_date',
 | 
			
		||||
            model_name="task",
 | 
			
		||||
            name="created_date",
 | 
			
		||||
            field=models.DateField(blank=True, default=django.utils.timezone.now, null=True),
 | 
			
		||||
        ),
 | 
			
		||||
        )
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,18 +5,15 @@ from django.db import migrations, models
 | 
			
		|||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('todo', '0008_mail_tracker'),
 | 
			
		||||
    ]
 | 
			
		||||
    dependencies = [("todo", "0008_mail_tracker")]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterModelOptions(
 | 
			
		||||
            name='task',
 | 
			
		||||
            options={'ordering': ['priority', 'created_date']},
 | 
			
		||||
            name="task", options={"ordering": ["priority", "created_date"]}
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='task',
 | 
			
		||||
            name='priority',
 | 
			
		||||
            model_name="task",
 | 
			
		||||
            name="priority",
 | 
			
		||||
            field=models.PositiveIntegerField(blank=True, null=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,18 +11,36 @@ class Migration(migrations.Migration):
 | 
			
		|||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
        ('todo', '0009_priority_optional'),
 | 
			
		||||
        ("todo", "0009_priority_optional"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Attachment',
 | 
			
		||||
            name="Attachment",
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('timestamp', models.DateTimeField(default=datetime.datetime.now)),
 | 
			
		||||
                ('file', models.FileField(max_length=255, upload_to=todo.models.get_attachment_upload_dir)),
 | 
			
		||||
                ('added_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
 | 
			
		||||
                ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='todo.Task')),
 | 
			
		||||
                (
 | 
			
		||||
                    "id",
 | 
			
		||||
                    models.AutoField(
 | 
			
		||||
                        auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                ("timestamp", models.DateTimeField(default=datetime.datetime.now)),
 | 
			
		||||
                (
 | 
			
		||||
                    "file",
 | 
			
		||||
                    models.FileField(
 | 
			
		||||
                        max_length=255, upload_to=todo.models.get_attachment_upload_dir
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "added_by",
 | 
			
		||||
                    models.ForeignKey(
 | 
			
		||||
                        on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                (
 | 
			
		||||
                    "task",
 | 
			
		||||
                    models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="todo.Task"),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
        ),
 | 
			
		||||
        )
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,9 +45,7 @@ class LockedAtomicTransaction(Atomic):
 | 
			
		|||
                cursor = get_connection(self.using).cursor()
 | 
			
		||||
                for model in self.models:
 | 
			
		||||
                    cursor.execute(
 | 
			
		||||
                        "LOCK TABLE {table_name}".format(
 | 
			
		||||
                            table_name=model._meta.db_table
 | 
			
		||||
                        )
 | 
			
		||||
                        "LOCK TABLE {table_name}".format(table_name=model._meta.db_table)
 | 
			
		||||
                    )
 | 
			
		||||
            finally:
 | 
			
		||||
                if cursor and not cursor.closed:
 | 
			
		||||
| 
						 | 
				
			
			@ -159,9 +157,7 @@ class Comment(models.Model):
 | 
			
		|||
    def snippet(self):
 | 
			
		||||
        body_snippet = textwrap.shorten(self.body, width=35, placeholder="...")
 | 
			
		||||
        # Define here rather than in __str__ so we can use it in the admin list_display
 | 
			
		||||
        return "{author} - {snippet}...".format(
 | 
			
		||||
            author=self.author_text, snippet=body_snippet
 | 
			
		||||
        )
 | 
			
		||||
        return "{author} - {snippet}...".format(author=self.author_text, snippet=body_snippet)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.snippet
 | 
			
		||||
| 
						 | 
				
			
			@ -173,9 +169,7 @@ class Attachment(models.Model):
 | 
			
		|||
    """
 | 
			
		||||
 | 
			
		||||
    task = models.ForeignKey(Task, on_delete=models.CASCADE)
 | 
			
		||||
    added_by = models.ForeignKey(
 | 
			
		||||
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE
 | 
			
		||||
    )
 | 
			
		||||
    added_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
 | 
			
		||||
    timestamp = models.DateTimeField(default=datetime.datetime.now)
 | 
			
		||||
    file = models.FileField(upload_to=get_attachment_upload_dir, max_length=255)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,11 @@ class CSVImporter:
 | 
			
		|||
                # newrow at this point is fully validated, and all FK relations exist,
 | 
			
		||||
                # e.g. `newrow.get("Assigned To")`, is a Django User instance.
 | 
			
		||||
                assignee = newrow.get("Assigned To") if newrow.get("Assigned To") else None
 | 
			
		||||
                created_date = newrow.get("Created Date") if newrow.get("Created Date") else datetime.datetime.today()
 | 
			
		||||
                created_date = (
 | 
			
		||||
                    newrow.get("Created Date")
 | 
			
		||||
                    if newrow.get("Created Date")
 | 
			
		||||
                    else datetime.datetime.today()
 | 
			
		||||
                )
 | 
			
		||||
                due_date = newrow.get("Due Date") if newrow.get("Due Date") else None
 | 
			
		||||
                priority = newrow.get("Priority") if newrow.get("Priority") else None
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +182,7 @@ class CSVImporter:
 | 
			
		|||
            row["Assigned To"] = assignee
 | 
			
		||||
 | 
			
		||||
        # Set Completed
 | 
			
		||||
        row["Completed"] = (row["Completed"] == "Yes")
 | 
			
		||||
        row["Completed"] = row["Completed"] == "Yes"
 | 
			
		||||
 | 
			
		||||
        # #######################
 | 
			
		||||
        if row_errors:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,24 +9,21 @@ from email.message import EmailMessage
 | 
			
		|||
 | 
			
		||||
def consumer(*args, title_format="[TEST] {subject}", **kwargs):
 | 
			
		||||
    return tracker_consumer(
 | 
			
		||||
        group="Workgroup One",
 | 
			
		||||
        task_list_slug="zip",
 | 
			
		||||
        priority=1,
 | 
			
		||||
        task_title_format=title_format,
 | 
			
		||||
        group="Workgroup One", task_list_slug="zip", priority=1, task_title_format=title_format
 | 
			
		||||
    )(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_message(subject, content):
 | 
			
		||||
    msg = EmailMessage()
 | 
			
		||||
    msg.set_content(content)
 | 
			
		||||
    msg['Subject'] = subject
 | 
			
		||||
    msg["Subject"] = subject
 | 
			
		||||
    return msg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_tracker_task_creation(todo_setup, django_user_model):
 | 
			
		||||
    msg = make_message("test1 subject", "test1 content")
 | 
			
		||||
    msg['From'] = 'test1@example.com'
 | 
			
		||||
    msg['Message-ID'] = '<a@example.com>'
 | 
			
		||||
    msg["From"] = "test1@example.com"
 | 
			
		||||
    msg["Message-ID"] = "<a@example.com>"
 | 
			
		||||
 | 
			
		||||
    # test task creation
 | 
			
		||||
    task_count = Task.objects.count()
 | 
			
		||||
| 
						 | 
				
			
			@ -38,30 +35,26 @@ def test_tracker_task_creation(todo_setup, django_user_model):
 | 
			
		|||
 | 
			
		||||
    # test thread answers
 | 
			
		||||
    msg = make_message("test2 subject", "test2 content")
 | 
			
		||||
    msg['From'] = 'test1@example.com'
 | 
			
		||||
    msg['Message-ID'] = '<b@example.com>'
 | 
			
		||||
    msg['References'] = '<nope@example.com> <a@example.com>'
 | 
			
		||||
    msg["From"] = "test1@example.com"
 | 
			
		||||
    msg["Message-ID"] = "<b@example.com>"
 | 
			
		||||
    msg["References"] = "<nope@example.com> <a@example.com>"
 | 
			
		||||
 | 
			
		||||
    task_count = Task.objects.count()
 | 
			
		||||
    consumer([msg])
 | 
			
		||||
    assert task_count == Task.objects.count(), "comment created another task"
 | 
			
		||||
    Comment.objects.get(
 | 
			
		||||
        task=task,
 | 
			
		||||
        body__contains="test2 content",
 | 
			
		||||
        email_message_id='<b@example.com>'
 | 
			
		||||
        task=task, body__contains="test2 content", email_message_id="<b@example.com>"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # test notification answer
 | 
			
		||||
    msg = make_message("test3 subject", "test3 content")
 | 
			
		||||
    msg['From'] = 'test1@example.com'
 | 
			
		||||
    msg['Message-ID'] = '<c@example.com>'
 | 
			
		||||
    msg['References'] = '<thread-{}@django-todo> <unknown@example.com>'.format(task.pk)
 | 
			
		||||
    msg["From"] = "test1@example.com"
 | 
			
		||||
    msg["Message-ID"] = "<c@example.com>"
 | 
			
		||||
    msg["References"] = "<thread-{}@django-todo> <unknown@example.com>".format(task.pk)
 | 
			
		||||
 | 
			
		||||
    task_count = Task.objects.count()
 | 
			
		||||
    consumer([msg])
 | 
			
		||||
    assert task_count == Task.objects.count(), "comment created another task"
 | 
			
		||||
    Comment.objects.get(
 | 
			
		||||
        task=task,
 | 
			
		||||
        body__contains="test3 content",
 | 
			
		||||
        email_message_id='<c@example.com>'
 | 
			
		||||
        task=task, body__contains="test3 content", email_message_id="<c@example.com>"
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,5 +55,6 @@ def test_send_email_to_thread_participants(todo_setup, django_user_model, email_
 | 
			
		|||
    assert "u3@example.com" in mail.outbox[0].recipients()
 | 
			
		||||
    assert "u4@example.com" in mail.outbox[0].recipients()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# FIXME: Add tests for:
 | 
			
		||||
# Attachments: Test whether allowed, test multiple, test extensions
 | 
			
		||||
| 
						 | 
				
			
			@ -166,6 +166,7 @@ def test_no_javascript_in_comments(todo_setup, client):
 | 
			
		|||
 | 
			
		||||
# ### PERMISSIONS ###
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_view_add_list_nonadmin(todo_setup, client):
 | 
			
		||||
    url = reverse("todo:add_list")
 | 
			
		||||
    client.login(username="you", password="password")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										99
									
								
								todo/urls.py
									
										
									
									
									
								
							
							
						
						
									
										99
									
								
								todo/urls.py
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -4,93 +4,46 @@ from django.urls import path
 | 
			
		|||
from todo import views
 | 
			
		||||
from todo.features import HAS_TASK_MERGE
 | 
			
		||||
 | 
			
		||||
app_name = 'todo'
 | 
			
		||||
app_name = "todo"
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path(
 | 
			
		||||
        '',
 | 
			
		||||
        views.list_lists,
 | 
			
		||||
        name="lists"),
 | 
			
		||||
 | 
			
		||||
    path("", views.list_lists, name="lists"),
 | 
			
		||||
    # View reorder_tasks is only called by JQuery for drag/drop task ordering.
 | 
			
		||||
    path(
 | 
			
		||||
        'reorder_tasks/',
 | 
			
		||||
        views.reorder_tasks,
 | 
			
		||||
        name="reorder_tasks"),
 | 
			
		||||
 | 
			
		||||
    path("reorder_tasks/", views.reorder_tasks, name="reorder_tasks"),
 | 
			
		||||
    # Allow users to post tasks from outside django-todo (e.g. for filing tickets - see docs)
 | 
			
		||||
    path(
 | 
			
		||||
        'ticket/add/',
 | 
			
		||||
        views.external_add,
 | 
			
		||||
        name="external_add"),
 | 
			
		||||
 | 
			
		||||
    path("ticket/add/", views.external_add, name="external_add"),
 | 
			
		||||
    # Three paths into `list_detail` view
 | 
			
		||||
    path("mine/", views.list_detail, {"list_slug": "mine"}, name="mine"),
 | 
			
		||||
    path(
 | 
			
		||||
        'mine/',
 | 
			
		||||
        "<int:list_id>/<str:list_slug>/completed/",
 | 
			
		||||
        views.list_detail,
 | 
			
		||||
        {'list_slug': 'mine'},
 | 
			
		||||
        name="mine"),
 | 
			
		||||
 | 
			
		||||
        {"view_completed": True},
 | 
			
		||||
        name="list_detail_completed",
 | 
			
		||||
    ),
 | 
			
		||||
    path("<int:list_id>/<str:list_slug>/", views.list_detail, name="list_detail"),
 | 
			
		||||
    path("<int:list_id>/<str:list_slug>/delete/", views.del_list, name="del_list"),
 | 
			
		||||
    path("add_list/", views.add_list, name="add_list"),
 | 
			
		||||
    path("task/<int:task_id>/", views.task_detail, name="task_detail"),
 | 
			
		||||
    path(
 | 
			
		||||
        '<int:list_id>/<str:list_slug>/completed/',
 | 
			
		||||
        views.list_detail,
 | 
			
		||||
        {'view_completed': True},
 | 
			
		||||
        name='list_detail_completed'),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        '<int:list_id>/<str:list_slug>/',
 | 
			
		||||
        views.list_detail,
 | 
			
		||||
        name='list_detail'),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        '<int:list_id>/<str:list_slug>/delete/',
 | 
			
		||||
        views.del_list,
 | 
			
		||||
        name="del_list"),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        'add_list/',
 | 
			
		||||
        views.add_list,
 | 
			
		||||
        name="add_list"),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        'task/<int:task_id>/',
 | 
			
		||||
        views.task_detail,
 | 
			
		||||
        name='task_detail'),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        'attachment/remove/<int:attachment_id>/',
 | 
			
		||||
        views.remove_attachment,
 | 
			
		||||
        name='remove_attachment'),
 | 
			
		||||
        "attachment/remove/<int:attachment_id>/", views.remove_attachment, name="remove_attachment"
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
if HAS_TASK_MERGE:
 | 
			
		||||
    # ensure mail tracker autocomplete is optional
 | 
			
		||||
    from todo.views.task_autocomplete import TaskAutocomplete
 | 
			
		||||
 | 
			
		||||
    urlpatterns.append(
 | 
			
		||||
        path(
 | 
			
		||||
            'task/<int:task_id>/autocomplete/',
 | 
			
		||||
            TaskAutocomplete.as_view(),
 | 
			
		||||
            name='task_autocomplete')
 | 
			
		||||
            "task/<int:task_id>/autocomplete/", TaskAutocomplete.as_view(), name="task_autocomplete"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
urlpatterns.extend([
 | 
			
		||||
    path(
 | 
			
		||||
        'toggle_done/<int:task_id>/',
 | 
			
		||||
        views.toggle_done,
 | 
			
		||||
        name='task_toggle_done'),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        'delete/<int:task_id>/',
 | 
			
		||||
        views.delete_task,
 | 
			
		||||
        name='delete_task'),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        'search/',
 | 
			
		||||
        views.search,
 | 
			
		||||
        name="search"),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        'import_csv/',
 | 
			
		||||
        views.import_csv,
 | 
			
		||||
        name="import_csv"),
 | 
			
		||||
])
 | 
			
		||||
urlpatterns.extend(
 | 
			
		||||
    [
 | 
			
		||||
        path("toggle_done/<int:task_id>/", views.toggle_done, name="task_toggle_done"),
 | 
			
		||||
        path("delete/<int:task_id>/", views.delete_task, name="delete_task"),
 | 
			
		||||
        path("search/", views.search, name="search"),
 | 
			
		||||
        path("import_csv/", views.import_csv, name="import_csv"),
 | 
			
		||||
    ]
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ def staff_check(user):
 | 
			
		|||
        https://github.com/shacker/django-todo/issues/50
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    if defaults('TODO_STAFF_ONLY'):
 | 
			
		||||
    if defaults("TODO_STAFF_ONLY"):
 | 
			
		||||
        return user.is_staff
 | 
			
		||||
    else:
 | 
			
		||||
        # If unset or False, allow all logged in users
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ def external_add(request) -> HttpResponse:
 | 
			
		|||
            task = form.save(commit=False)
 | 
			
		||||
            task.task_list = TaskList.objects.get(slug=settings.TODO_DEFAULT_LIST_SLUG)
 | 
			
		||||
            task.created_by = request.user
 | 
			
		||||
            if defaults('TODO_DEFAULT_ASSIGNEE'):
 | 
			
		||||
            if defaults("TODO_DEFAULT_ASSIGNEE"):
 | 
			
		||||
                task.assigned_to = User.objects.get(username=settings.TODO_DEFAULT_ASSIGNEE)
 | 
			
		||||
            task.save()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,10 +18,7 @@ def remove_attachment(request, attachment_id: int) -> HttpResponse:
 | 
			
		|||
    if request.method == "POST":
 | 
			
		||||
        attachment = get_object_or_404(Attachment, pk=attachment_id)
 | 
			
		||||
 | 
			
		||||
        redir_url = reverse(
 | 
			
		||||
            "todo:task_detail",
 | 
			
		||||
            kwargs={"task_id": attachment.task.id},
 | 
			
		||||
        )
 | 
			
		||||
        redir_url = reverse("todo:task_detail", kwargs={"task_id": attachment.task.id})
 | 
			
		||||
 | 
			
		||||
        # Permissions
 | 
			
		||||
        if not (
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +30,9 @@ def remove_attachment(request, attachment_id: int) -> HttpResponse:
 | 
			
		|||
        if remove_attachment_file(attachment.id):
 | 
			
		||||
            messages.success(request, f"Attachment {attachment.id} removed.")
 | 
			
		||||
        else:
 | 
			
		||||
            messages.error(request, f"Sorry, there was a problem deleting attachment {attachment.id}.")
 | 
			
		||||
            messages.error(
 | 
			
		||||
                request, f"Sorry, there was a problem deleting attachment {attachment.id}."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return redirect(redir_url)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,13 +121,13 @@ def task_detail(request, task_id: int) -> HttpResponse:
 | 
			
		|||
    if request.FILES.get("attachment_file_input"):
 | 
			
		||||
        file = request.FILES.get("attachment_file_input")
 | 
			
		||||
 | 
			
		||||
        if file.size > defaults('TODO_MAXIMUM_ATTACHMENT_SIZE'):
 | 
			
		||||
        if file.size > defaults("TODO_MAXIMUM_ATTACHMENT_SIZE"):
 | 
			
		||||
            messages.error(request, f"File exceeds maximum attachment size.")
 | 
			
		||||
            return redirect("todo:task_detail", task_id=task.id)
 | 
			
		||||
 | 
			
		||||
        name, extension = os.path.splitext(file.name)
 | 
			
		||||
 | 
			
		||||
        if extension not in defaults('TODO_LIMIT_FILE_ATTACHMENTS'):
 | 
			
		||||
        if extension not in defaults("TODO_LIMIT_FILE_ATTACHMENTS"):
 | 
			
		||||
            messages.error(request, f"This site does not allow upload of {extension} files.")
 | 
			
		||||
            return redirect("todo:task_detail", task_id=task.id)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +144,7 @@ def task_detail(request, task_id: int) -> HttpResponse:
 | 
			
		|||
        "merge_form": merge_form,
 | 
			
		||||
        "thedate": thedate,
 | 
			
		||||
        "comment_classes": defaults("TODO_COMMENT_CLASSES"),
 | 
			
		||||
        "attachments_enabled": defaults('TODO_ALLOW_FILE_ATTACHMENTS'),
 | 
			
		||||
        "attachments_enabled": defaults("TODO_ALLOW_FILE_ATTACHMENTS"),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return render(request, "todo/task_detail.html", context)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue