diff --git a/.gitignore b/.gitignore index b090e3d..eb2764f 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,7 @@ venv.bak/ #postgres-data -postgres-data/ \ No newline at end of file +postgres-data/ + +#vscode Visual Code Studio +.vscode/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e9b4ac4..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Python: Flask", - "type": "python", - "request": "launch", - "module": "flask", - "env": { - "FLASK_APP": "app.py", - "FLASK_ENV": "development", - "FLASK_DEBUG": "0" - }, - "args": [ - "run", - "--no-debugger", - "--no-reload" - ], - "jinja": true - } - ] -} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d2d6a4c..037cc14 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,12 +14,13 @@ services: command: postgres -c listen_addresses='*' web: build: . - command: python manage.py runserver 0.0.0.0:8000 + command: sh -c "python manage.py wait_for_db && python manage.py runserver 0.0.0.0:8000" volumes: - .:/code ports: - "8000:8000" depends_on: + - db - migration migration: build: . diff --git a/shop/admin.py b/shop/admin.py index fae09bd..817759c 100644 --- a/shop/admin.py +++ b/shop/admin.py @@ -2,6 +2,8 @@ from django.contrib.admin import AdminSite from .models import (Category, Product, OrderItem, Order, Cart, CartItem, User) +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.utils.translation import gettext as _ class PythonShopAdminSite(AdminSite): @@ -13,9 +15,8 @@ class PythonShopAdminSite(AdminSite): class ProductAdmin(admin.ModelAdmin): list_display = [field.name for field in Product._meta.fields - if field.name != "id" or field.name != 'updated_at' - or field.name != 'created_at' - or field.name != 'deleted_at' + if field.name != "id" or field.name != 'updated_at' or + field.name != 'created_at' or field.name != 'deleted_at' ] exclude = ('deleted_at',) @@ -24,6 +25,29 @@ class CategoryAdmin(admin.ModelAdmin): exclude = ('deleted_at',) +class UserAdmin(BaseUserAdmin): + ordering = ['id'] + list_display = ['email', 'firstname'] + fieldsets = ( + (None, {'fields': ('email', 'password')}), + ( + _('Personal Info'), + {'fields': ('firstname', 'lastname', 'second_lastname')} + ), + ( + _('Permissions'), + {'fields': ('is_active', 'is_staff', 'is_superuser')} + ), + (_('Important dates'), {'fields': ('last_login',)}) + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('email', 'password1', 'password2') + }), + ) + + admin_site = PythonShopAdminSite(name='shop_admin') # Register your models here. @@ -33,4 +57,4 @@ class CategoryAdmin(admin.ModelAdmin): admin_site.register(OrderItem) admin_site.register(Cart) admin_site.register(CartItem) -admin_site.register(User) +admin_site.register(User, UserAdmin) diff --git a/shop/management/__init__.py b/shop/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shop/management/commands/__init__.py b/shop/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shop/management/commands/wait_for_db.py b/shop/management/commands/wait_for_db.py new file mode 100644 index 0000000..94a9e79 --- /dev/null +++ b/shop/management/commands/wait_for_db.py @@ -0,0 +1,21 @@ +import time + +from django.db import connections +from django.db.utils import OperationalError +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + """Django commando to pause execution until database is available""" + + def handle(self, *args, **options): + self.stdout.write('Waiting for database...') + db_conn = None + while not db_conn: + try: + db_conn = connections['default'] + except OperationalError: + self.stdout.write('Database unavailable, waiting 1 second...') + time.sleep(1) + + self.stdout.write(self.style.SUCCESS('Database available!')) diff --git a/shop/tests/__init__.py b/shop/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shop/tests/test_admin.py b/shop/tests/test_admin.py new file mode 100644 index 0000000..cd274fa --- /dev/null +++ b/shop/tests/test_admin.py @@ -0,0 +1,41 @@ +from django.test import TestCase, Client +from django.contrib.auth import get_user_model +from django.urls import reverse + + +class AdminSiteTests(TestCase): + + def setUp(self): + self.client = Client() + self.admin_user = get_user_model().objects.create_superuser( + email='test@test.com', + password='test123' + ) + self.client.force_login(self.admin_user) + self.user = get_user_model().objects.create_user( + email='another@test.com', + password='password123', + firstname='Testing first name' + ) + + def test_users_listed(self): + """Test that users are listed on user page of admin""" + url = reverse('admin:shop_user_changelist') + response = self.client.get(url) + + self.assertContains(response, self.user.firstname) + self.assertContains(response, self.user.email) + + def test_user_change_page(self): + """Test that the user edit page works""" + url = reverse('admin:shop_user_change', args=[self.user.id]) + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + + def test_create_user_page(self): + """Test that the create user page works""" + url = reverse('admin:shop_user_add') + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) diff --git a/shop/tests/test_commands.py b/shop/tests/test_commands.py new file mode 100644 index 0000000..1ccd0d9 --- /dev/null +++ b/shop/tests/test_commands.py @@ -0,0 +1,23 @@ +from unittest.mock import patch + +from django.core.management import call_command +from django.db.utils import OperationalError +from django.test import TestCase + + +class CommandTests(TestCase): + + def test_wait_for_db_ready(self): + """Test waiting for db when db is available""" + with patch('django.db.utils.ConnectionHandler.__getitem__') as gi: + gi.return_value = True + call_command('wait_for_db') + self.assertEqual(gi.call_count, 1) + + @patch('time.sleep', return_value=True) + def test_wait_for_db(self, ts): + """Test waiting for db""" + with patch('django.db.utils.ConnectionHandler.__getitem__') as gi: + gi.side_effect = [OperationalError] * 5 + [True] + call_command('wait_for_db') + self.assertEqual(gi.call_count, 6) diff --git a/shop/tests.py b/shop/tests/test_models.py similarity index 100% rename from shop/tests.py rename to shop/tests/test_models.py