diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py index 1285891d323..d60db0fce5e 100644 --- a/evennia/accounts/tests.py +++ b/evennia/accounts/tests.py @@ -67,11 +67,19 @@ def setUp(self): self.s1 = MagicMock() self.s1.puppet = None self.s1.sessid = 0 - self.s1.data_outj - def tearDown(self): - if hasattr(self, "account"): - self.account.delete() + def test_absolute_url(self): + "Get URL for account detail page on website" + self.account = create.create_account("TestAccount%s" % randint(100000, 999999), + email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.assertTrue(self.account.web_get_detail_url()) + + def test_admin_url(self): + "Get object's URL for access via Admin pane" + self.account = create.create_account("TestAccount%s" % randint(100000, 999999), + email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.assertTrue(self.account.web_get_admin_url()) + self.assertTrue(self.account.web_get_admin_url() != '#') def test_password_validation(self): "Check password validators deny bad passwords" diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 3d0b9e02b7a..7e1a13722e7 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -7,11 +7,15 @@ """ import time import inflect +import re from builtins import object from future.utils import with_metaclass from collections import defaultdict from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse +from django.utils.text import slugify from evennia.typeclasses.models import TypeclassBase from evennia.typeclasses.attributes import NickHandler @@ -324,7 +328,7 @@ def get_numbered_name(self, count, looker, **kwargs): # look at 'an egg'. self.aliases.add(singular, category="plural_key") return singular, plural - + def search(self, searchdata, global_search=False, use_nicks=True, @@ -1825,7 +1829,6 @@ class DefaultCharacter(DefaultObject): a character avatar controlled by an account. """ - def basetype_setup(self): """ Setup character-specific security. @@ -1942,7 +1945,6 @@ class DefaultRoom(DefaultObject): This is the base room object. It's just like any Object except its location is always `None`. """ - def basetype_setup(self): """ Simple room setup setting locks to make sure the room diff --git a/evennia/objects/tests.py b/evennia/objects/tests.py new file mode 100644 index 00000000000..824007f2874 --- /dev/null +++ b/evennia/objects/tests.py @@ -0,0 +1,11 @@ +from evennia.utils.test_resources import EvenniaTest + +class DefaultObjectTest(EvenniaTest): + + def test_urls(self): + "Make sure objects are returning URLs" + self.assertTrue(self.char1.get_absolute_url()) + self.assertTrue('admin' in self.char1.web_get_admin_url()) + + self.assertTrue(self.room1.get_absolute_url()) + self.assertTrue('admin' in self.room1.web_get_admin_url()) \ No newline at end of file diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index c156aa2ac6d..d549efb8ff3 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -31,9 +31,12 @@ class needs to supply a ForeignKey field attr_object pointing to the kind from django.db.models.base import ModelBase from django.db import models +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.conf import settings +from django.urls import reverse from django.utils.encoding import smart_str +from django.utils.text import slugify from evennia.typeclasses.attributes import Attribute, AttributeHandler, NAttributeHandler from evennia.typeclasses.tags import Tag, TagHandler, AliasHandler, PermissionHandler @@ -733,3 +736,135 @@ def at_rename(self, oldname, newname): """ pass + + # + # Web/Django methods + # + + def web_get_admin_url(self): + """ + Returns the URI path for the Django Admin page for this object. + + ex. Account#1 = '/admin/accounts/accountdb/1/change/' + + Returns: + path (str): URI path to Django Admin page for object. + + """ + content_type = ContentType.objects.get_for_model(self.__class__) + return reverse("admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,)) + + @classmethod + def web_get_create_url(cls): + """ + Returns the URI path for a View that allows users to create new + instances of this object. + + ex. Chargen = '/characters/create/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-create' would be referenced by this method. + + ex. + url(r'characters/create/', ChargenView.as_view(), name='character-create') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can create new objects is the + developer's responsibility. + + Returns: + path (str): URI path to object creation page, if defined. + + """ + try: return reverse('%s-create' % cls._meta.verbose_name.lower()) + except: return '#' + + def web_get_detail_url(self): + """ + Returns the URI path for a View that allows users to view details for + this object. + + ex. Oscar (Character) = '/characters/oscar/1/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-detail' would be referenced by this method. + + ex. + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/$', CharDetailView.as_view(), name='character-detail') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can view this object is the developer's + responsibility. + + Returns: + path (str): URI path to object detail page, if defined. + + """ + try: return reverse('%s-detail' % self._meta.verbose_name.lower(), kwargs={'pk': self.pk, 'slug': slugify(self.name)}) + except: return '#' + + def web_get_update_url(self): + """ + Returns the URI path for a View that allows users to update this + object. + + ex. Oscar (Character) = '/characters/oscar/1/change/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-update' would be referenced by this method. + + ex. + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/change/$', CharUpdateView.as_view(), name='character-update') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can modify objects is the developer's + responsibility. + + Returns: + path (str): URI path to object update page, if defined. + + """ + try: return reverse('%s-update' % self._meta.verbose_name.lower(), kwargs={'pk': self.pk, 'slug': slugify(self.name)}) + except: return '#' + + def web_get_delete_url(self): + """ + Returns the URI path for a View that allows users to delete this object. + + ex. Oscar (Character) = '/characters/oscar/1/delete/' + + For this to work, the developer must have defined a named view somewhere + in urls.py that follows the format 'modelname-action', so in this case + a named view of 'character-detail' would be referenced by this method. + + ex. + url(r'characters/(?P[\w\d\-]+)/(?P[0-9]+)/delete/$', CharDeleteView.as_view(), name='character-delete') + + If no View has been created and defined in urls.py, returns an + HTML anchor. + + This method is naive and simply returns a path. Securing access to + the actual view and limiting who can delete this object is the developer's + responsibility. + + Returns: + path (str): URI path to object deletion page, if defined. + + """ + try: return reverse('%s-delete' % self._meta.verbose_name.lower(), kwargs={'pk': self.pk, 'slug': slugify(self.name)}) + except: return '#' + + # Used by Django Sites/Admin + get_absolute_url = web_get_detail_url \ No newline at end of file