diff --git a/apps/models.py b/apps/models.py index 9589c0b26..7d921b2c7 100755 --- a/apps/models.py +++ b/apps/models.py @@ -69,6 +69,17 @@ class Meta: 'apps/img/app_icon_generic.png') +APP_TYPE_DESKTOP = 'desktop' +APP_TYPE_WEB = 'web' +APP_TYPE_SERVICE = 'service' + +APP_TYPE_CHOICES = [ + (APP_TYPE_DESKTOP, 'Desktop'), + (APP_TYPE_WEB, 'Web'), + (APP_TYPE_SERVICE, 'Service'), +] + + def app_icon_path(app, filename): """ Callable function used by :py:class:`~App` constructor @@ -124,6 +135,13 @@ class App(models.Model): active = models.BooleanField(default=False) + app_type = models.CharField( + max_length=10, + choices=APP_TYPE_CHOICES, + default=APP_TYPE_DESKTOP, + db_index=True, + ) + def is_editor(self, user): """ Denotes if 'user' passed in can edit this App diff --git a/apps/tests.py b/apps/tests.py index e9dfc1803..467d21aca 100755 --- a/apps/tests.py +++ b/apps/tests.py @@ -13,10 +13,11 @@ from PIL import Image, ImageDraw from django.conf import settings +from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.contrib.auth.models import User from django.test import TestCase -from apps.models import App +from apps.models import App, APP_TYPE_DESKTOP, APP_TYPE_WEB, APP_TYPE_SERVICE from apps.models import Author from apps.models import OrderedAuthor from apps.models import Screenshot @@ -676,4 +677,65 @@ def test_app_button_by_name_found_app(self): active=True) appobj.save() res = app_buttons.app_button_by_name('myapp') - self.assertEqual('myapp', res['app'].name) \ No newline at end of file + self.assertEqual('myapp', res['app'].name) + + +class AppTypeTestCase(TestCase): + + def setUp(self): + App.objects.all().delete() + + def tearDown(self): + App.objects.all().delete() + + def test_default_app_type_is_desktop(self): + appobj = App.objects.create(name='myapp', fullname='MyApp') + self.assertEqual(APP_TYPE_DESKTOP, appobj.app_type) + + def test_app_type_persists_as_web(self): + App.objects.create(name='myapp', fullname='MyApp', + app_type=APP_TYPE_WEB) + appobj = App.objects.get(name='myapp') + self.assertEqual(APP_TYPE_WEB, appobj.app_type) + + def test_app_type_persists_as_service(self): + App.objects.create(name='myapp', fullname='MyApp', + app_type=APP_TYPE_SERVICE) + appobj = App.objects.get(name='myapp') + self.assertEqual(APP_TYPE_SERVICE, appobj.app_type) + + def test_invalid_app_type_fails_validation(self): + appobj = App.objects.create(name='myapp', fullname='MyApp', + app_type='invalid') + with self.assertRaises(ValidationError): + appobj.full_clean() + + def test_filter_by_app_type_desktop(self): + App.objects.create(name='desktop1', fullname='Desktop One', + app_type=APP_TYPE_DESKTOP) + App.objects.create(name='web1', fullname='Web One', + app_type=APP_TYPE_WEB) + App.objects.create(name='service1', fullname='Service One', + app_type=APP_TYPE_SERVICE) + results = App.objects.filter(app_type=APP_TYPE_DESKTOP) + self.assertEqual(1, results.count()) + self.assertEqual('desktop1', results.first().name) + + def test_filter_by_app_type_web(self): + App.objects.create(name='web1', fullname='Web One', + app_type=APP_TYPE_WEB) + App.objects.create(name='web2', fullname='Web Two', + app_type=APP_TYPE_WEB) + App.objects.create(name='desktop1', fullname='Desktop One', + app_type=APP_TYPE_DESKTOP) + results = App.objects.filter(app_type=APP_TYPE_WEB) + self.assertEqual(2, results.count()) + + def test_filter_by_app_type_service(self): + App.objects.create(name='service1', fullname='Service One', + app_type=APP_TYPE_SERVICE) + App.objects.create(name='desktop1', fullname='Desktop One', + app_type=APP_TYPE_DESKTOP) + results = App.objects.filter(app_type=APP_TYPE_SERVICE) + self.assertEqual(1, results.count()) + self.assertEqual('service1', results.first().name) \ No newline at end of file diff --git a/dbmigration/add_app_type.py b/dbmigration/add_app_type.py new file mode 100644 index 000000000..837f79448 --- /dev/null +++ b/dbmigration/add_app_type.py @@ -0,0 +1,44 @@ +# Adds app_type column to apps_app table. +# +# app_type replaces the insufficient is_service boolean and supports three +# platform states: 'desktop' (default), 'web', and 'service'. +# +# Existing rows are defaulted to 'desktop' to preserve backward compatibility. +# +# Usage: +# python add_app_type.py +# +# Make sure to update the connection credentials below before running. + +import MySQLdb as mdb + +DB_HOST = 'localhost' +DB_USER = 'root' +DB_PASS = '' +DB_NAME = 'CyAppStore' + +con = None +try: + con = mdb.connect(DB_HOST, DB_USER, DB_PASS, DB_NAME) + cur = con.cursor() + + cur.execute(""" + ALTER TABLE apps_app + ADD COLUMN app_type VARCHAR(10) NOT NULL DEFAULT 'desktop' + AFTER active + """) + + cur.execute(""" + ALTER TABLE apps_app + ADD INDEX apps_app_app_type_idx (app_type) + """) + + con.commit() + print("Migration successful: app_type column added to apps_app.") +except mdb.Error as e: + print(f"Migration failed: {e}") + if con: + con.rollback() +finally: + if con: + con.close()