diff --git a/.gitignore b/.gitignore index 8312747..90f1258 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,8 @@ bin/ lib64 pyvenv.cfg share/ +pip-selfcheck.json +MEDIA/ # PyInstaller # Usually these files are written by a python script from a template @@ -91,3 +93,7 @@ ENV/ # Rope project settings .ropeproject + +# Ignore the files in the Media directory, but not the directory itself +imagersite/MEDIA/* +!imagersite/MEDIA/.gitkeep \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ffad062 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: python +python: + - "2.7" + - "3.5" + +# command to install dependencies +install: + # - pip install . + - pip install -r requirements.pip + +services: + - postgresql + +before_script: + - psql -c 'create database travis_ci_test;' -U postgres + +# command to run tests +script: python imagersite/manage.py test \ No newline at end of file diff --git a/README.md b/README.md index 552d117..9152cf3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# django-imager -django introduction assignment +[](https://travis-ci.org/pasaunders/django-imager) +# Django Imager +### Getting Started + +Clone this repository into whatever directory you want to work from. + +```bash +$ git clone https://github.com/pasaunders/django-imager.git +``` + +Assuming that you have access to Python 3 at the system level, start up a new virtual environment. + +```bash +$ cd django-imager +$ python3 -m venv . +$ source bin/activate +``` + +Once your environment has been activated, make sure to install Django and all of this project's required packages. + +```bash +(django-imager) $ pip install -r requirements.pip +``` + +Navigate to the project root, `imagersite`, and apply the migrations for the app. + +```bash +(django-imager) $ cd imagersite +(django-imager) $ ./manage.py migrate +``` + +Finally, run the server in order to server the app on `localhost` + +```bash +(django-imager) $ ./manage.py runserver +``` + +Django will typically serve on port 8000, unless you specify otherwise. +You can access the locally-served site at the address `http://localhost:8000`. + +Resources we used: +http://stackoverflow.com/questions/10180764/django-auth-login-problems \ No newline at end of file diff --git a/imagersite/MEDIA/.gitkeep b/imagersite/MEDIA/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/imagersite/imager_images/__init__.py b/imagersite/imager_images/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/imagersite/imager_images/admin.py b/imagersite/imager_images/admin.py new file mode 100644 index 0000000..aae142c --- /dev/null +++ b/imagersite/imager_images/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from imager_images.models import Album, Photo + +admin.site.register(Album) +admin.site.register(Photo) diff --git a/imagersite/imager_images/apps.py b/imagersite/imager_images/apps.py new file mode 100644 index 0000000..7afb70a --- /dev/null +++ b/imagersite/imager_images/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ImagerImagesConfig(AppConfig): + name = 'imager_images' diff --git a/imagersite/imager_images/migrations/0001_initial.py b/imagersite/imager_images/migrations/0001_initial.py new file mode 100644 index 0000000..1fa1ba8 --- /dev/null +++ b/imagersite/imager_images/migrations/0001_initial.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-19 05:16 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import imager_images.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Album', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=60)), + ('description', models.TextField(max_length=200)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_modified', models.DateTimeField(auto_now=True)), + ('date_published', models.DateTimeField(null=True)), + ('published', models.CharField(choices=[('private', 'private'), ('shared', 'shared'), ('public', 'public')], max_length=10)), + ], + ), + migrations.CreateModel( + name='Photo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(upload_to=imager_images.models.image_path)), + ('title', models.CharField(max_length=60)), + ('description', models.TextField(max_length=120)), + ('date_uploaded', models.DateTimeField(auto_now_add=True)), + ('date_modified', models.DateTimeField(auto_now=True)), + ('date_published', models.DateTimeField(null=True)), + ('published', models.CharField(choices=[('private', 'private'), ('shared', 'shared'), ('public', 'public')], max_length=10)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='album', + name='cover', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='albums_covered', to='imager_images.Photo'), + ), + migrations.AddField( + model_name='album', + name='photos', + field=models.ManyToManyField(related_name='albums', to='imager_images.Photo'), + ), + migrations.AddField( + model_name='album', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='albums', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/imagersite/imager_images/migrations/0002_auto_20170129_1248.py b/imagersite/imager_images/migrations/0002_auto_20170129_1248.py new file mode 100644 index 0000000..a14027a --- /dev/null +++ b/imagersite/imager_images/migrations/0002_auto_20170129_1248.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-29 20:48 +from __future__ import unicode_literals + +from django.db import migrations, models +import imager_images.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('imager_images', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='album', + name='date_published', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='album', + name='published', + field=models.CharField(choices=[('private', 'private'), ('shared', 'shared'), ('public', 'public')], default='public', max_length=10), + ), + migrations.AlterField( + model_name='photo', + name='date_modified', + field=models.DateTimeField(auto_now=True, null=True), + ), + migrations.AlterField( + model_name='photo', + name='date_published', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='photo', + name='date_uploaded', + field=models.DateTimeField(auto_now_add=True, null=True), + ), + migrations.AlterField( + model_name='photo', + name='description', + field=models.TextField(blank=True, max_length=120, null=True), + ), + migrations.AlterField( + model_name='photo', + name='image', + field=models.ImageField(blank=True, null=True, upload_to=imager_images.models.image_path), + ), + migrations.AlterField( + model_name='photo', + name='published', + field=models.CharField(choices=[('private', 'private'), ('shared', 'shared'), ('public', 'public')], default='public', max_length=10), + ), + ] diff --git a/imagersite/imager_images/migrations/__init__.py b/imagersite/imager_images/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/imagersite/imager_images/models.py b/imagersite/imager_images/models.py new file mode 100644 index 0000000..40554a7 --- /dev/null +++ b/imagersite/imager_images/models.py @@ -0,0 +1,68 @@ +from __future__ import unicode_literals +from django.utils.encoding import python_2_unicode_compatible +from django.db import models +from django.contrib.auth.models import User + +PUBLISHED_OPTIONS = ( + ("private", "private"), + ("shared", "shared"), + ("public", "public"), +) + + +def image_path(instance, file_name): + """Upload file to media root in user folder.""" + return 'user_{0}/{1}'.format(instance.user.id, file_name) + + +@python_2_unicode_compatible +class Photo(models.Model): + """Create Photo Model.""" + + user = models.ForeignKey( + User, + related_name='photos', + on_delete=models.CASCADE, + ) + image = models.ImageField(upload_to=image_path, blank=True, null=True) + title = models.CharField(max_length=60) + description = models.TextField(max_length=120, blank=True, null=True) + date_uploaded = models.DateTimeField(auto_now_add=True, blank=True, null=True) + date_modified = models.DateTimeField(auto_now=True, blank=True, null=True) + date_published = models.DateTimeField(null=True, blank=True) + published = models.CharField(max_length=10, choices=PUBLISHED_OPTIONS, default='public') + + def __str__(self): + """Return string description of album.""" + return "{}: Photo belonging to {}".format(self.title, self.user) + + +@python_2_unicode_compatible +class Album(models.Model): + """Create Album Model.""" + + user = models.ForeignKey( + User, + related_name="albums", + on_delete=models.CASCADE, + ) + cover = models.ForeignKey( + "Photo", + null=True, + related_name="albums_covered" + ) + title = models.CharField(max_length=60) + description = models.TextField(max_length=200) + photos = models.ManyToManyField( + "Photo", + related_name="albums", + symmetrical=False + ) + date_created = models.DateTimeField(auto_now_add=True) + date_modified = models.DateTimeField(auto_now=True) + date_published = models.DateTimeField(null=True, blank=True) + published = models.CharField(max_length=10, choices=PUBLISHED_OPTIONS, default="public") + + def __str__(self): + """Return String Representation of Album.""" + return "{}: Album belonging to {}".format(self.title, self.user) diff --git a/imagersite/imager_images/test_img.jpg b/imagersite/imager_images/test_img.jpg new file mode 100644 index 0000000..fb59ba4 Binary files /dev/null and b/imagersite/imager_images/test_img.jpg differ diff --git a/imagersite/imager_images/tests.py b/imagersite/imager_images/tests.py new file mode 100644 index 0000000..9cbf9cc --- /dev/null +++ b/imagersite/imager_images/tests.py @@ -0,0 +1,278 @@ +"""Test the imager_images app.""" +from django.test import TestCase, Client, RequestFactory +from django.contrib.auth.models import User +from django.core.files.uploadedfile import SimpleUploadedFile +from imager_images.models import Photo, Album +import factory + + +class UserFactory(factory.django.DjangoModelFactory): + """Makes users.""" + + class Meta: + """Meta.""" + + model = User + + username = factory.Sequence(lambda n: "Prisoner number {}".format(n)) + email = factory.LazyAttribute( + lambda x: "{}@foo.com".format(x.username.replace(" ", "")) + ) + + +class PhotoFactory(factory.django.DjangoModelFactory): + """Makes photos.""" + + class Meta: + """Meta.""" + + model = Photo + + user = factory.SubFactory(UserFactory) + title = factory.Sequence(lambda n: "Photo number {}".format(n)) + description = factory.LazyAttribute(lambda a: '{} is a photo'.format(a.title)) + + +class AlbumFacotory(factory.django.DjangoModelFactory): + """Makes albums.""" + + class Meta: + """Meta.""" + + model = Album + + user = factory.SubFactory(UserFactory) + title = factory.Sequence(lambda n: "Album number {}".format(n)) + description = factory.LazyAttribute(lambda a: '{} is an album'.format(a.title)) + + +class PhotoTestCase(TestCase): + """Photo model and view tests.""" + + def setUp(self): + """The appropriate setup for the appropriate test.""" + self.users = [UserFactory.create() for i in range(20)] + self.photos = [PhotoFactory.create() for i in range(20)] + + def test_photo_made_when_saved(self): + """Test photos are added to the database.""" + self.assertTrue(Photo.objects.count() == 20) + + def test_photo_associated_with_user(self): + """Test that a photo is attached to a user.""" + photo = Photo.objects.first() + self.assertTrue(hasattr(photo, "__str__")) + + def test_photo_has_str(self): + """Test photo model includes string method.""" + photo = Photo.objects.first() + self.assertTrue(hasattr(photo, "user")) + + def test_image_title(self): + """Test that the image has a title.""" + self.assertTrue("Photo" in Photo.objects.first().title) + + def test_image_has_description(self): + """Test that the Photo description field can be assigned.""" + image = Photo.objects.first() + description = "This is a test of description field." + image.description = description + image.save() + self.assertTrue(Photo.objects.first().description == description) + + def test_image_has_published(self): + """Test the image published field.""" + image = Photo.objects.first() + image.published = 'public' + image.save() + self.assertTrue(Photo.objects.first().published == "public") + + def test_user_has_image(self): + """Test that the user has the image.""" + image = Photo.objects.first() + user = User.objects.first() + self.assertTrue(user.photos.count() == 0) + image.user = user + image.save() + self.assertTrue(user.photos.count() == 1) + + def test_two_images_have_user(self): + """Test two images have the same user.""" + image1 = Photo.objects.all()[0] + image2 = Photo.objects.all()[1] + user = User.objects.first() + image1.user = user + image2.user = user + image1.save() + image2.save() + self.assertTrue(image1.user == user) + self.assertTrue(image2.user == user) + + def test_user_has_two_images(self): + """Test that user has two image.""" + image1 = Photo.objects.all()[0] + image2 = Photo.objects.all()[1] + user = User.objects.first() + image1.user = user + image2.user = user + image1.save() + image2.save() + self.assertTrue(user.photos.count() == 2) + + def test_user_has_photo_uploaded(self): + """Test user has photo uploaded.""" + photo = self.photos[4] + self.assertTrue(photo.image.name is None) + image = SimpleUploadedFile( + name='test_image.jpg', + content=open('imager_images/test_img.jpg', 'rb').read(), + content_type='image/jpeg' + ) + photo.image = image + self.assertTrue(photo.image.name is not None) + + +class AlbumTestCase(TestCase): + """Album model and view tests.""" + + def setUp(self): + """The appropriate setup for the appropriate test.""" + self.users = [UserFactory.create() for i in range(20)] + self.photos = [PhotoFactory.create() for i in range(20)] + self.albums = [AlbumFacotory.create() for i in range(20)] + + def test_image_has_no_album(self): + """Test that the image is in an album.""" + image = Photo.objects.first() + self.assertTrue(image.albums.count() == 0) + + def test_image_has_album(self): + """Test that the image is in an album.""" + image = Photo.objects.first() + album = Album.objects.first() + image.albums.add(album) + self.assertTrue(image.albums.count() == 1) + + def test_album_has_no_image(self): + """Test that an album has no image before assignemnt.""" + album = Album.objects.first() + self.assertTrue(album.photos.count() == 0) + + def test_album_has_image(self): + """Test that an album has an image after assignemnt.""" + image = Photo.objects.first() + album = Album.objects.first() + image.albums.add(album) + self.assertTrue(image.albums.count() == 1) + + def test_two_images_have_album(self): + """Test that two images have same album.""" + image1 = Photo.objects.all()[0] + image2 = Photo.objects.all()[1] + album = Album.objects.first() + image1.albums.add(album) + image2.albums.add(album) + image1.save() + image2.save() + self.assertTrue(image1.albums.all()[0] == album) + self.assertTrue(image2.albums.all()[0] == album) + + def test_album_has_two_images(self): + """Test that an album has two images.""" + image1 = Photo.objects.all()[0] + image2 = Photo.objects.all()[1] + album = Album.objects.first() + image1.albums.add(album) + image2.albums.add(album) + image1.save() + image2.save() + self.assertTrue(album.photos.count() == 2) + + def test_image_has_two_albums(self): + """Test that an image has two albums.""" + image = Photo.objects.first() + album1 = Album.objects.all()[0] + album2 = Album.objects.all()[1] + image.albums.add(album1) + image.albums.add(album2) + image.save() + self.assertTrue(image.albums.count() == 2) + + def test_album_title(self): + """Test that the album has a title.""" + self.assertTrue("Album" in Album.objects.first().title) + + def test_album_has_description(self): + """Test that the album description field exists.""" + self.assertTrue("is an album" in Album.objects.first().description) + + def test_album_has_published(self): + """Test that the album published field exists.""" + album = Album.objects.first() + album.published = 'public' + album.save() + self.assertTrue(Album.objects.first().published == "public") + + def test_album_has_user(self): + """Test that album has an user.""" + self.assertTrue(Album.objects.first().user) + + def test_user_has_album(self): + """Test that the user has the album.""" + album = Album.objects.first() + user = User.objects.first() + self.assertTrue(user.albums.count() == 0) + album.user = user + album.save() + self.assertTrue(user.albums.count() == 1) + + def test_two_albums_have_user(self): + """Test two albums have the same user.""" + album1 = Album.objects.all()[0] + album2 = Album.objects.all()[1] + user = User.objects.first() + album1.user = user + album2.user = user + album1.save() + album2.save() + self.assertTrue(album1.user == user) + self.assertTrue(album2.user == user) + + def test_user_has_two_albums(self): + """Test that user has two albums.""" + album1 = Album.objects.all()[0] + album2 = Album.objects.all()[1] + user = User.objects.first() + album1.user = user + album2.user = user + album1.save() + album2.save() + self.assertTrue(user.albums.count() == 2) + + def test_adding_cover_image(self): + """Test that the image is in an album.""" + image = Photo.objects.first() + album = Album.objects.first() + # import ipdb; ipdb.set_trace() + self.assertTrue(album.cover is None) + album.cover = image + album.save() + self.assertTrue(Album.objects.first().cover is not None) + + +class FrontEndTestCase(TestCase): + """Front end tests.""" + + def setUp(self): + """Set up client and requestfactory.""" + self.client = Client() + self.request = RequestFactory() + + """ + To test: + Views return 200 + Routes return 200 + all four templates are used + albums are visible in albums.html + correct number of photos and albums are visible + """ diff --git a/imagersite/imager_images/views.py b/imagersite/imager_images/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/imagersite/imager_images/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/imagersite/imager_profile/__init__.py b/imagersite/imager_profile/__init__.py new file mode 100644 index 0000000..4a991a4 --- /dev/null +++ b/imagersite/imager_profile/__init__.py @@ -0,0 +1 @@ +default_app_config = 'imager_profile.apps.ImagerProfileConfig' diff --git a/imagersite/imager_profile/admin.py b/imagersite/imager_profile/admin.py new file mode 100644 index 0000000..8295835 --- /dev/null +++ b/imagersite/imager_profile/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from imager_profile.models import ImagerProfile + +# Register your models here. + + +admin.site.register(ImagerProfile) diff --git a/imagersite/imager_profile/apps.py b/imagersite/imager_profile/apps.py new file mode 100644 index 0000000..8900e6d --- /dev/null +++ b/imagersite/imager_profile/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ImagerProfileConfig(AppConfig): + name = 'imager_profile' diff --git a/imagersite/imager_profile/migrations/0001_initial.py b/imagersite/imager_profile/migrations/0001_initial.py new file mode 100644 index 0000000..94299f0 --- /dev/null +++ b/imagersite/imager_profile/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-29 01:26 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ImagerProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('camera_type', models.CharField(blank=True, choices=[('Nikon', 'Nikon'), ('iPhone', 'iPhone'), ('Canon', 'Canon'), ('--------', '--------')], default='--------', max_length=10)), + ('address', models.CharField(blank=True, default='', max_length=70, null=True)), + ('bio', models.TextField(default='')), + ('personal_website', models.URLField(default='')), + ('for_hire', models.BooleanField(default=False)), + ('travel_distance', models.IntegerField(blank=True, default=0)), + ('phone_number', models.CharField(blank=True, default='', max_length=15)), + ('photography_type', models.CharField(blank=True, default='', max_length=20)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/imagersite/imager_profile/migrations/__init__.py b/imagersite/imager_profile/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/imagersite/imager_profile/models.py b/imagersite/imager_profile/models.py new file mode 100644 index 0000000..72578c9 --- /dev/null +++ b/imagersite/imager_profile/models.py @@ -0,0 +1,73 @@ +"""Models for imager_profile.""" + +from django.db import models +from django.contrib.auth.models import User +from django.utils.encoding import python_2_unicode_compatible +from django.db.models.signals import post_save +from django.dispatch import receiver + +# Create your models here. + + +class ActiveProfileManager(models.Manager): + """Create Model Manager for Active Profiles.""" + + def get_queryset(self): + """Return active users.""" + qs = super(ActiveProfileManager, self).get_queryset() + return qs.filter(user__is_active__exact=True) + + +@python_2_unicode_compatible +class ImagerProfile(models.Model): + """The imager user and all their attributes.""" + + objects = models.Manager() + active = ActiveProfileManager() + + user = models.OneToOneField( + User, + related_name="profile", + on_delete=models.CASCADE + ) + CAMERA_CHOICES = [ + ('Nikon', 'Nikon'), + ('iPhone', 'iPhone'), + ('Canon', 'Canon'), + ('--------', '--------') + ] + TYPE_OF_PHOTOGRAPHY = [ + ('nature', 'nature'), + ('urban', 'urban'), + ('portraits', 'portraits') + ] + camera_type = models.CharField( + max_length=10, + choices=CAMERA_CHOICES, + blank=True, + default='--------' + ) + address = models.CharField(default="", max_length=70, null=True, blank=True) + bio = models.TextField(default="") + personal_website = models.URLField(default="") + for_hire = models.BooleanField(default=False) + travel_distance = models.IntegerField(default=0, blank=True) + phone_number = models.CharField(max_length=15, default="", blank=True) + photography_type = models.CharField(max_length=20, default="", blank=True) + + @property + def is_active(self): + """Return True if user associated with this profile is active.""" + return self.user.is_active + + def __str__(self): + """Display user data as a string.""" + return "User: {}, Camera: {}, Address: {}, Phone number: {}, For Hire? {}, Photography style: {}".format(self.user, self.camera_type, self.address, self.phone_number, self.for_hire, self.photography_type) + + +@receiver(post_save, sender=User) +def make_profile_for_user(sender, instance, **kwargs): + """Called when user is made and hooks that user to a profile.""" + if kwargs["created"]: + new_profile = ImagerProfile(user=instance) + new_profile.save() diff --git a/imagersite/imager_profile/tests.py b/imagersite/imager_profile/tests.py new file mode 100644 index 0000000..8ebf27a --- /dev/null +++ b/imagersite/imager_profile/tests.py @@ -0,0 +1,184 @@ +"""Tests for the imager_profile app.""" +from django.test import TestCase, Client, RequestFactory +from django.contrib.auth.models import User +from imager_profile.models import ImagerProfile +import factory + + +class UserFactory(factory.django.DjangoModelFactory): + """Makes users.""" + + class Meta: + """Metadata for UserFactory.""" + + model = User + + username = factory.Sequence(lambda n: "Prisoner number {}".format(n)) + email = factory.LazyAttribute( + lambda x: "{}@foo.com".format(x.username.replace(" ", "")) + ) + + +class ProfileTestCase(TestCase): + """The Profile Model test runner.""" + + def setUp(self): + """The appropriate setup for the appropriate test.""" + self.users = [UserFactory.create() for i in range(20)] + + def test_profile_is_made_when_user_is_saved(self): + """Test profile is made when user is saved.""" + self.assertTrue(ImagerProfile.objects.count() == 20) + + def test_profile_is_associated_with_actual_users(self): + """Test profile is associated with actual users.""" + profile = ImagerProfile.objects.first() + self.assertTrue(hasattr(profile, "user")) + self.assertIsInstance(profile.user, User) + + def test_user_has_profile_attached(self): + """Test user has profile attached.""" + user = self.users[0] + self.assertTrue(hasattr(user, "profile")) + self.assertIsInstance(user.profile, ImagerProfile) + + def test_user_model_has_str(self): + """Test user has a string method.""" + user = self.users[0] + self.assertIsInstance(str(user), str) + + def test_active_users_counted(self): + """Test acttive user count meets expectations.""" + self.assertTrue(ImagerProfile.active.count() == User.objects.count()) + + def test_inactive_users_not_counted(self): + """Test inactive users not included with active users.""" + deactivated_user = self.users[0] + deactivated_user.is_active = False + deactivated_user.save() + self.assertTrue(ImagerProfile.active.count() == User.objects.count() - 1) + + def test_imagerprofile_attributes(self): + """Test that ImagerProfile has the expected attributes.""" + attribute_list = ["user", "camera_type", "address", "bio", "personal_website", "for_hire", "travel_distance", "phone_number", "photography_type"] + for item in attribute_list: + self.assertTrue(hasattr(ImagerProfile, item)) + + def test_field_type(self): + """Test user field types.""" + attribute_list = ["camera_type", "address", "bio", "personal_website", "for_hire", "travel_distance", "phone_number", "photography_type"] + field_list = [str, str, str, str, bool, int, str, str] + test_user = self.users[0] + # import pdb; pdb.set_trace() + self.assertIsInstance(test_user.username, str) + for attribute, field in zip(attribute_list, field_list): + self.assertIsInstance(getattr(test_user.profile, attribute), field) + + +class FrontendTestCases(TestCase): + """Test the frontend of the imager_profile site.""" + + def setUp(self): + """Set up client and request factory.""" + self.client = Client() + self.request = RequestFactory() + + def test_home_view_status(self): + """Test home view has 200 status.""" + from imagersite.views import home_view + req = self.request.get("/route") + response = home_view(req) + self.assertTrue(response.status_code == 200) + + def test_home_route_status(self): + """Test home route has 200 status.""" + response = self.client.get("/") + self.assertTrue(response.status_code == 200) + + def test_home_route_templates(self): + """Test the home route templates are correct.""" + response = self.client.get("/") + self.assertTemplateUsed(response, "imagersite/home.html") + + def test_login_template(self): + """Test the login route templates are correct.""" + response = self.client.get("/login/") + self.assertTemplateUsed(response, "registration/login.html") + + def test_registration_template(self): + """Test the login route templates are correct.""" + response = self.client.get("/accounts/register/") + self.assertTemplateUsed(response, "imagersite/base.html") + self.assertTemplateUsed(response, "registration/registration_form.html") + + def test_login_redirect_code(self): + """Test built-in login route redirects properly.""" + user_register = UserFactory.create() + user_register.is_active = True + user_register.username = "username" + user_register.set_password("potatoes") + user_register.save() + response = self.client.post("/login/", { + "username": user_register.username, + "password": "potatoes" + }) + self.assertRedirects(response, '/') + + def test_register_user(self): + """Test that tests can register users.""" + self.assertTrue(User.objects.count() == 0) + self.client.post("/accounts/register/", { + "username": "Sir_Joseph", + "email": "e@mail.com", + "password1": "rutabega", + "password2": "rutabega" + }) + self.assertTrue(User.objects.count() == 1) + + def test_new_user_inactive(self): + """Test django-created user starts as inactive.""" + self.client.post("/accounts/register/", { + "username": "Sir_Joseph", + "email": "e@mail.com", + "password1": "rutabega", + "password2": "rutabega" + }) + inactive_user = User.objects.first() + self.assertFalse(inactive_user.is_active) + + def test_registration_redirect(self): + """Test redirect on registration.""" + response = self.client.post("/accounts/register/", { + "username": "Sir_Joseph", + "email": "e@mail.com", + "password1": "rutabega", + "password2": "rutabega" + }) + self.assertTrue(response.status_code == 302) + + def test_registration_reidrect_home(self): + """Test registration redirects home.""" + response = self.client.post("/accounts/register/", { + "username": "Sir_Joseph", + "email": "e@mail.com", + "password1": "rutabega", + "password2": "rutabega" + }, follow=True) + self.assertRedirects( + response, + "/accounts/register/complete/" + ) + + def test_logout_redirects_to_home(self): + """Test logging out redirects to home.""" + user_register = UserFactory.create() + user_register.is_active = True + user_register.username = "username" + user_register.set_password("potatoes") + user_register.save() + self.client.post("/login/", { + "username": user_register.username, + "password": "potatoes" + }) + response = self.client.get('/logout/') + self.assertRedirects(response, '/') diff --git a/imagersite/imager_profile/views.py b/imagersite/imager_profile/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/imagersite/imager_profile/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/imagersite/imagersite/__init__.py b/imagersite/imagersite/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/imagersite/imagersite/settings.py b/imagersite/imagersite/settings.py new file mode 100644 index 0000000..4274c76 --- /dev/null +++ b/imagersite/imagersite/settings.py @@ -0,0 +1,142 @@ +""" +Django settings for imagersite project. + +Generated by 'django-admin startproject' using Django 1.10.5. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.10/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '5m(o9rkvq-2$u452_313+-m9&gn*8%mqj+&^yum=r!%h%@(%!j' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'bootstrap3', + 'imager_profile', + 'imagersite', + 'imager_images' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'imagersite.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'imagersite.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.10/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': os.environ['IMAGER_DATABASE'], + # 'USER': os.environ['DATABASE_USER'], + # 'PASSWORD': os.environ['DATABASE_PASSWORD'], + # 'HOST': '127.0.0.1', + # 'PORT': '5432', + 'TEST': { + 'NAME': os.environ['TEST_IMAGER_DATABASE'] + } + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.10/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'America/Los_Angeles' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.10/howto/static-files/ + +STATIC_URL = '/static/' + + +# Registration Settings +ACCOUNT_ACTIVATION_DAYS = 3 +EMAIL_HOST = '127.0.0.1' +EMAIL_PORT = 1025 + +# Login/out settings +LOGIN_REDIRECT_URL = '/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'MEDIA') +MEDIA_URL = "/media/" diff --git a/imagersite/imagersite/templates/imagersite/base.html b/imagersite/imagersite/templates/imagersite/base.html new file mode 100644 index 0000000..cfea42a --- /dev/null +++ b/imagersite/imagersite/templates/imagersite/base.html @@ -0,0 +1,9 @@ + + +
+