diff --git a/Pipfile b/Pipfile index 215d884..b0e6883 100644 --- a/Pipfile +++ b/Pipfile @@ -1,24 +1,19 @@ [[source]] - url = "https://pypi.python.org/simple" verify_ssl = true name = "pypi" - [packages] - django = "*" django-extensions = "*" "psycopg2-binary" = "*" pytest = "*" pytest-django = "*" "flake8" = "*" - +factory-boy = "*" +titlecase = "*" [dev-packages] - - [requires] - python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock index 4a380a7..f43c48c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,20 +1,7 @@ { "_meta": { "hash": { - "sha256": "5a7237884b6690e23782690ae05e2d2ffd38a04d142f9fccc926860494073e9b" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.6.3", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "17.4.0", - "platform_system": "Darwin", - "platform_version": "Darwin Kernel Version 17.4.0: Sun Dec 17 09:19:54 PST 2017; root:xnu-4570.41.2~1/RELEASE_X86_64", - "python_full_version": "3.6.3", - "python_version": "3.6", - "sys_platform": "darwin" + "sha256": "8aa62fe5923a75a6df757c643dbd98177e66bd0bc9e429d65006f042f73f5a32" }, "pipfile-spec": 6, "requires": { @@ -31,30 +18,48 @@ "default": { "attrs": { "hashes": [ - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" + "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", + "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450" ], "version": "==17.4.0" }, "django": { "hashes": [ - "sha256:3d9916515599f757043c690ae2b5ea28666afa09779636351da505396cbb2f19", - "sha256:769f212ffd5762f72c764fa648fca3b7f7dd4ec27407198b68e7c4abf4609fd0" + "sha256:2d8b9eed8815f172a8e898678ae4289a5e9176bc08295676eff4228dd638ea61", + "sha256:d81a1652963c81488e709729a80b510394050e312f386037f26b54912a3a10d0" ], - "version": "==2.0.3" + "index": "pypi", + "version": "==2.0.4" }, "django-extensions": { "hashes": [ "sha256:37a543af370ee3b0721ff50442d33c357dd083e6ea06c5b94a199283b6f9e361", "sha256:bc9f2946c117bb2f49e5e0633eba783787790ae810ea112fe7fd82fa64de2ff1" ], + "index": "pypi", "version": "==2.0.6" }, + "factory-boy": { + "hashes": [ + "sha256:bd5a096d0f102d79b6c78cef1c8c0b650f2e1a3ecba351c735c6d2df8dabd29c", + "sha256:be2abc8092294e4097935a29b4e37f5b9ed3e4205e2e32df215c0315b625995e" + ], + "index": "pypi", + "version": "==2.10.0" + }, + "faker": { + "hashes": [ + "sha256:9cc12b821f32ff45f6edfdc1ab7be3893b60b1224e952d68322a57e5b26a4a15", + "sha256:b06d0dc0166618298e668ced513ced7b10df34f3ad2045f22f1d7d88704e8e9c" + ], + "version": "==0.8.12" + }, "flake8": { "hashes": [ - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37", - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0" + "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", + "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" ], + "index": "pypi", "version": "==3.5.0" }, "mccabe": { @@ -66,8 +71,8 @@ }, "more-itertools": { "hashes": [ - "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", + "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" ], "version": "==4.1.0" @@ -80,47 +85,48 @@ }, "psycopg2-binary": { "hashes": [ - "sha256:b287ddf4cafcfb632974907d1e7862119e36bb758228bdb07dd247553e4cdfc0", - "sha256:d1dd3eb8edd354083f5d27b968c5a17854c41347ba5a480b520be85ec1a8495c", - "sha256:cf3911fba0c47fc1313b5783183cda301032b14637a0b7a336766ae46998c7ee", - "sha256:b039f51bca1ddd70234cc3f84f94f42ad43861b931bdfb497f887c60c39a6565", - "sha256:83af04029bcb4b56c852e5876fef71340dcb465fa44fc99f80bac72e10fb0b74", - "sha256:3a14baeabcebd4662f12f4bff03e0574a2369a2e41baf829e6fb4a24c95cf88b", - "sha256:cb07184a4bfad304831f0a88b1c13fbd8cf9fcdf1f11e71c477dd6d7b1b078a0", - "sha256:d0972f062c73956332e9681dfdb133168618f0abfecc96e89f0205ac89cd454b", - "sha256:ab1db8f3e96570d9f7ebc45133ce2574804b2280499baade178e163d022107b5", - "sha256:d51c7ed810fce1e50464088c37cc8da05534de8afb12a732500827ebcc480081", - "sha256:b6b2b26590304d97ef2af28d153ee99ace6fe0806934f4618edfc87216c77f91", - "sha256:9b5ddbed85ec73293695d7116589d956ef0dd3fcf7bf3b2a3bc1e8e54c1d543a", "sha256:02eb674e3d5810e19b4d5d00720b17130e182da1ba259dda608aaf33d787347d", + "sha256:3a14baeabcebd4662f12f4bff03e0574a2369a2e41baf829e6fb4a24c95cf88b", "sha256:436a503eda41f6adb08f292f40a3784fce0a5f351b6ae7b19a911904db53af93", - "sha256:8014c06a9ed7b78ba81beff3ae71acd78c212390f8ed839e9ce22735880bd5b4", - "sha256:c4c6004d410c77bfa5389ae9485498ce32805447a67afbfe8db0d247a5c88fa1", - "sha256:d8940b5104588d6313315e037f0f5ed68d2e5f62ccc1c429d3cff11d2ba6de3f", - "sha256:c606bff0978ee4858d86d40f6b6ab0c4cac4474f627bd054683dc03a4fc1a366", - "sha256:9305d7cbc802aaefac5c75a3df725f2654797369f32b18d4d0adb382dfab6c09", - "sha256:4a1a5ea2fa4b53191637b162873a82822d92a85a08beefe28296b8eb5cf2fea5", - "sha256:a3d2cc0cb0b988dbfd0d11f7fac34058b25a6ce533ed5b8e88d6cb315e77d54a", - "sha256:86c0d2587f56776f25d52cca8e275adf495c8e01933fbfc2ca23b124610ab761", - "sha256:77a2fc622a1f2d08a707673c9be5769d521f03d867d305f172bb417fa7882754", - "sha256:4a4f23a08fbccbe40ecdb5384d807bcb469ea71dd87e6be2e80b036b8e6d47df", - "sha256:c8220c521a408b41c4f14036004a621ed0d965941286b978cd2ea2623fabd755", "sha256:465ff1d427ed42c31e456dbbd9edab3552be18a0edaef7450c5b3e6fee745052", + "sha256:4a1a5ea2fa4b53191637b162873a82822d92a85a08beefe28296b8eb5cf2fea5", + "sha256:4a4f23a08fbccbe40ecdb5384d807bcb469ea71dd87e6be2e80b036b8e6d47df", + "sha256:77a2fc622a1f2d08a707673c9be5769d521f03d867d305f172bb417fa7882754", + "sha256:8014c06a9ed7b78ba81beff3ae71acd78c212390f8ed839e9ce22735880bd5b4", + "sha256:83af04029bcb4b56c852e5876fef71340dcb465fa44fc99f80bac72e10fb0b74", + "sha256:86c0d2587f56776f25d52cca8e275adf495c8e01933fbfc2ca23b124610ab761", + "sha256:9305d7cbc802aaefac5c75a3df725f2654797369f32b18d4d0adb382dfab6c09", + "sha256:9b5ddbed85ec73293695d7116589d956ef0dd3fcf7bf3b2a3bc1e8e54c1d543a", + "sha256:a3d2cc0cb0b988dbfd0d11f7fac34058b25a6ce533ed5b8e88d6cb315e77d54a", + "sha256:ab1db8f3e96570d9f7ebc45133ce2574804b2280499baade178e163d022107b5", + "sha256:b039f51bca1ddd70234cc3f84f94f42ad43861b931bdfb497f887c60c39a6565", + "sha256:b287ddf4cafcfb632974907d1e7862119e36bb758228bdb07dd247553e4cdfc0", + "sha256:b6b2b26590304d97ef2af28d153ee99ace6fe0806934f4618edfc87216c77f91", + "sha256:c4c6004d410c77bfa5389ae9485498ce32805447a67afbfe8db0d247a5c88fa1", + "sha256:c606bff0978ee4858d86d40f6b6ab0c4cac4474f627bd054683dc03a4fc1a366", + "sha256:c8220c521a408b41c4f14036004a621ed0d965941286b978cd2ea2623fabd755", + "sha256:cb07184a4bfad304831f0a88b1c13fbd8cf9fcdf1f11e71c477dd6d7b1b078a0", + "sha256:cf3911fba0c47fc1313b5783183cda301032b14637a0b7a336766ae46998c7ee", + "sha256:d0972f062c73956332e9681dfdb133168618f0abfecc96e89f0205ac89cd454b", + "sha256:d1dd3eb8edd354083f5d27b968c5a17854c41347ba5a480b520be85ec1a8495c", + "sha256:d51c7ed810fce1e50464088c37cc8da05534de8afb12a732500827ebcc480081", + "sha256:d8940b5104588d6313315e037f0f5ed68d2e5f62ccc1c429d3cff11d2ba6de3f", "sha256:de4f88f823037a71ea5ef3c1041d96b8a68d73343133edda684fd42f575bd9d7" ], + "index": "pypi", "version": "==2.7.4" }, "py": { "hashes": [ - "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a", - "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881" + "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", + "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" ], "version": "==1.5.3" }, "pycodestyle": { "hashes": [ - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" ], "version": "==2.3.1" }, @@ -136,6 +142,7 @@ "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1" ], + "index": "pypi", "version": "==3.5.0" }, "pytest-django": { @@ -143,28 +150,51 @@ "sha256:00995c2999b884a38ae9cd30a8c00ed32b3d38c1041250ea84caf18085589662", "sha256:038ccc5a9daa1b1b0eb739ab7dce54e495811eca5ea3af4815a2a3ac45152309" ], + "index": "pypi", "version": "==3.1.2" }, + "python-dateutil": { + "hashes": [ + "sha256:3220490fb9741e2342e1cf29a503394fdac874bc39568288717ee67047ff29df", + "sha256:9d8074be4c993fbe4947878ce593052f71dac82932a677d49194d8ce9778002e" + ], + "version": "==2.7.2" + }, "pytz": { "hashes": [ - "sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe", - "sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda", - "sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9", - "sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f", "sha256:07edfc3d4d2705a20a6e99d97f0c4b61c800b8232dc1c04d87e8554f130148dd", "sha256:3a47ff71597f821cd84a162e71593004286e5be07a340fd462f0d33a760782b5", + "sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0", "sha256:5bd55c744e6feaa4d599a6cbd8228b4f8f9ba96de2c38d56f08e534b3c9edf0d", + "sha256:61242a9abc626379574a166dc0e96a66cd7c3b27fc10868003fa210be4bff1c9", "sha256:887ab5e5b32e4d0c86efddd3d055c1f363cbaa583beb8da5e22d2fa2f64d51ef", - "sha256:410bcd1d6409026fbaa65d9ed33bf6dd8b1e94a499e32168acfc7b332e4095c0" + "sha256:ba18e6a243b3625513d85239b3e49055a2f0318466e0b8a92b8fb8ca7ccdf55f", + "sha256:ed6509d9af298b7995d69a440e2822288f2eca1681b8cce37673dbb10091e5fe", + "sha256:f93ddcdd6342f94cea379c73cddb5724e0d6d0a1c91c9bdef364dc0368ba4fda" ], "version": "==2018.3" }, "six": { "hashes": [ - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" ], "version": "==1.11.0" + }, + "text-unidecode": { + "hashes": [ + "sha256:5a1375bb2ba7968740508ae38d92e1f889a0832913cb1c447d5e2046061a396d", + "sha256:801e38bd550b943563660a91de8d4b6fa5df60a542be9093f7abf819f86050cc" + ], + "version": "==1.2" + }, + "titlecase": { + "hashes": [ + "sha256:84de7a97fb702c400e5ba11c6b30849944b39db12e20fbf4515a23c7538a0611", + "sha256:95d643a0c08097c02933aced707adfe1c275c335019e8e514dea782a465c5b84" + ], + "index": "pypi", + "version": "==0.12.0" } }, "develop": {} diff --git a/todo/forms.py b/todo/forms.py index bb988e7..3aea30b 100644 --- a/todo/forms.py +++ b/todo/forms.py @@ -1,8 +1,7 @@ from django import forms -from django.forms import ModelForm from django.contrib.auth.models import Group +from django.forms import ModelForm from todo.models import Task, TaskList -from django.contrib.auth import get_user_model class AddTaskListForm(ModelForm): @@ -18,16 +17,18 @@ class AddTaskListForm(ModelForm): class Meta: model = TaskList - exclude = [] + exclude = ['created_date', ] class AddEditTaskForm(ModelForm): """The picklist showing the users to which a new task can be assigned - must find other members of the groups the current list belongs to.""" + must find other members of the group this TaskList is attached to.""" def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=user.groups.all()).distinct() + task_list = kwargs.get('initial').get('task_list') + members = task_list.group.user_set.all() + self.fields['assigned_to'].queryset = members self.fields['assigned_to'].label_from_instance = lambda obj: "%s (%s)" % (obj.get_full_name(), obj.username) self.fields['assigned_to'].widget.attrs = { 'id': 'id_assigned_to', 'class': "custom-select mb-3", 'name': 'assigned_to'} diff --git a/todo/management/commands/hopper.py b/todo/management/commands/hopper.py index c38a581..d9f2811 100644 --- a/todo/management/commands/hopper.py +++ b/todo/management/commands/hopper.py @@ -1,7 +1,39 @@ +import factory +from faker import Faker +from titlecase import titlecase +import random + from django.core.management.base import BaseCommand from django.contrib.auth.models import Group -from todo.models import Task, TaskList from django.contrib.auth import get_user_model +from django.utils.text import slugify + +from todo.models import Task, TaskList + + +num_lists = 5 +num_tasks_per_list = 10 + + +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('.') + if tc: + thestr = titlecase(thestr) + + return thestr + + +def gen_content(): + # faker provides paragraphs as a list; convert with linebreaks + fake = Faker() + grafs = fake.paragraphs() + thestr = '' + for g in grafs: + thestr += "{}\n\n".format(g) + return thestr class Command(BaseCommand): @@ -9,25 +41,99 @@ class Command(BaseCommand): def handle(self, *args, **options): + # Wipe out previous contents. Cascade deletes the Tasks from the TaskLists. + TaskList.objects.all().delete() + print("Content from previous run deleted.") + print("Working...") + + fake = Faker() # Use to create user's names + # Create users and groups, add different users to different groups. Staff user is in both groups. - bw_group = Group.objects.create(name='Basket Weavers') - sd_group = Group.objects.create(name='Scuba Divers') - usernames = ['user1', 'user2', 'staff_user'] - + 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'] for username in usernames: - get_user_model().objects.create_user(username=username, password="todo") + if get_user_model().objects.filter(username=username).exists(): + user = get_user_model().objects.get(username=username) + else: + user = get_user_model().objects.create_user( + username=username, + first_name=fake.first_name(), + last_name=fake.last_name(), + email=fake.email(), + password="todo") - if username == 'user1': - u1 = get_user_model().objects.get(username=username) - u1.groups.add(bw_group) + if username in ['user1', 'user2']: + user.groups.add(bw_group) - if username == 'user2': - u2 = get_user_model().objects.get(username=username) - u2.groups.add(sd_group) + if username in ['user3', 'user4']: + user.groups.add(sd_group) - if username == 'staff_user': - staffer = get_user_model().objects.get(username=username, is_staff=True,) - staffer.groups.add(bw_group) - staffer.groups.add(sd_group) + if username == 'staffer': + user.is_staff = True + user.first_name = fake.first_name() + user.last_name = fake.last_name() + user.save() + user.groups.add(bw_group) + user.groups.add(sd_group) + + # Create lists with tasks + TaskListFactory.create_batch(5, group=bw_group) + TaskListFactory.create_batch(5, group=sd_group) + + print("For each of two groups, created {} fake tasks in each of {} fake lists.".format( + num_lists, num_tasks_per_list) + ) +class TaskListFactory(factory.django.DjangoModelFactory): + """Group not generated here - call with group as arg.""" + + class Meta: + model = TaskList + + name = factory.LazyAttribute(lambda o: gen_title(tc=True)) + slug = factory.LazyAttribute(lambda o: slugify(o.name)) + group = None # Pass this in + + @factory.post_generation + def add_tasks(self, build, extracted, **kwargs): + TaskFactory.create_batch(num_tasks_per_list, task_list=self) + + +class TaskFactory(factory.django.DjangoModelFactory): + """TaskList not generated here - call with TaskList as arg.""" + + class Meta: + model = Task + + title = factory.LazyAttribute(lambda o: gen_title(tc=False)) + 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 = 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): + + fake = Faker() # Use to create user's names + taskgroup = self.task_list.group + + self.created_by = taskgroup.user_set.all().order_by('?').first() + + if self.completed: + self.completed_date = fake.date_this_year() + + # 1/3 of generated tasks have a due_date + if random.randint(1, 3) == 1: + self.due_date = fake.date_this_year() + + # 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.save() diff --git a/todo/migrations/0007_auto_update_created_date.py b/todo/migrations/0007_auto_update_created_date.py new file mode 100644 index 0000000..dfee145 --- /dev/null +++ b/todo/migrations/0007_auto_update_created_date.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.4 on 2018-04-05 00:24 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo', '0006_rename_item_model'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='created_date', + field=models.DateField(blank=True, default=django.utils.timezone.now, null=True), + ), + ] diff --git a/todo/models.py b/todo/models.py index c2a324f..3a8d558 100644 --- a/todo/models.py +++ b/todo/models.py @@ -5,6 +5,7 @@ from django.conf import settings from django.contrib.auth.models import Group from django.db import models from django.urls import reverse +from django.utils import timezone class TaskList(models.Model): @@ -26,7 +27,7 @@ class TaskList(models.Model): class Task(models.Model): title = models.CharField(max_length=140) task_list = models.ForeignKey(TaskList, on_delete=models.CASCADE, null=True) - created_date = models.DateField(auto_now=True) + created_date = models.DateField(default=timezone.now, blank=True, null=True) due_date = models.DateField(blank=True, null=True, ) completed = models.BooleanField(default=False) completed_date = models.DateField(blank=True, null=True) diff --git a/todo/templates/todo/include/task_edit.html b/todo/templates/todo/include/task_edit.html index 6ea11f8..30f7913 100644 --- a/todo/templates/todo/include/task_edit.html +++ b/todo/templates/todo/include/task_edit.html @@ -3,6 +3,7 @@