diff --git a/assets/pasted-20251127-205145-18826df4.jpg b/assets/pasted-20251127-205145-18826df4.jpg new file mode 100644 index 0000000..8c45f62 Binary files /dev/null and b/assets/pasted-20251127-205145-18826df4.jpg differ diff --git a/assets/pasted-20251127-205619-41747550.jpg b/assets/pasted-20251127-205619-41747550.jpg new file mode 100644 index 0000000..aad2de5 Binary files /dev/null and b/assets/pasted-20251127-205619-41747550.jpg differ diff --git a/assets/pasted-20251127-233622-995d9058.jpg b/assets/pasted-20251127-233622-995d9058.jpg new file mode 100644 index 0000000..a4b9e54 Binary files /dev/null and b/assets/pasted-20251127-233622-995d9058.jpg differ diff --git a/assets/vm-shot-2025-11-27T20-51-05-255Z.jpg b/assets/vm-shot-2025-11-27T20-51-05-255Z.jpg new file mode 100644 index 0000000..8c45f62 Binary files /dev/null and b/assets/vm-shot-2025-11-27T20-51-05-255Z.jpg differ diff --git a/assets/vm-shot-2025-11-27T20-56-14-538Z.jpg b/assets/vm-shot-2025-11-27T20-56-14-538Z.jpg new file mode 100644 index 0000000..aad2de5 Binary files /dev/null and b/assets/vm-shot-2025-11-27T20-56-14-538Z.jpg differ diff --git a/assets/vm-shot-2025-11-27T21-11-08-694Z.jpg b/assets/vm-shot-2025-11-27T21-11-08-694Z.jpg new file mode 100644 index 0000000..a4b9e54 Binary files /dev/null and b/assets/vm-shot-2025-11-27T21-11-08-694Z.jpg differ diff --git a/assets/vm-shot-2025-11-27T23-35-39-351Z.jpg b/assets/vm-shot-2025-11-27T23-35-39-351Z.jpg new file mode 100644 index 0000000..a4b9e54 Binary files /dev/null and b/assets/vm-shot-2025-11-27T23-35-39-351Z.jpg differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 5be02db..27bd8ae 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 28817aa..ee5a3e6 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..a7ef340 100644 --- a/config/settings.py +++ b/config/settings.py @@ -58,6 +58,8 @@ INSTALLED_APPS = [ 'core', ] +AUTH_USER_MODEL = 'core.User' + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -110,6 +112,8 @@ DATABASES = { }, } +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators @@ -180,3 +184,8 @@ if EMAIL_USE_SSL: # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +LOGIN_URL = 'login' +LOGIN_REDIRECT_URL = 'core:index' +LOGOUT_REDIRECT_URL = 'login' + diff --git a/config/urls.py b/config/urls.py index bcfc074..7e410b6 100644 --- a/config/urls.py +++ b/config/urls.py @@ -22,6 +22,7 @@ from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), path("", include("core.urls")), + path("", include("django.contrib.auth.urls")), ] if settings.DEBUG: diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 9aa598b..9a7d3a4 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 1f807fa..81fedb5 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 6867ddf..595a222 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/management/__init__.py b/core/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/__pycache__/__init__.cpython-311.pyc b/core/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..c512eb0 Binary files /dev/null and b/core/management/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/management/commands/assign_group.py b/core/management/commands/assign_group.py new file mode 100644 index 0000000..2ad9f3a --- /dev/null +++ b/core/management/commands/assign_group.py @@ -0,0 +1,32 @@ + +from django.core.management.base import BaseCommand +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group + +User = get_user_model() + +class Command(BaseCommand): + help = 'Assign a user to a group' + + def add_arguments(self, parser): + parser.add_argument('username', type=str, help='The username of the user') + parser.add_argument('group_name', type=str, help='The name of the group') + + def handle(self, *args, **options): + username = options['username'] + group_name = options['group_name'] + + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + self.stdout.write(self.style.ERROR(f'User "{username}" does not exist')) + return + + try: + group = Group.objects.get(name=group_name) + except Group.DoesNotExist: + self.stdout.write(self.style.ERROR(f'Group "{group_name}" does not exist')) + return + + user.groups.add(group) + self.stdout.write(self.style.SUCCESS(f'Successfully assigned user "{username}" to group "{group_name}"')) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..7563638 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,55 @@ +# Generated by Django 5.2.7 on 2025-11-27 23:25 + +import django.contrib.auth.models +import django.contrib.auth.validators +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='Organization', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('logo', models.ImageField(blank=True, null=True, upload_to='organization_logos/')), + ], + ), + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('user_type', models.CharField(choices=[('internal', 'Internal'), ('client', 'Client')], max_length=10)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='core_user_set', related_query_name='user', to='auth.group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='core_user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), + ('organization', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='users', to='core.organization')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/core/migrations/0002_create_groups.py b/core/migrations/0002_create_groups.py new file mode 100644 index 0000000..88e865c --- /dev/null +++ b/core/migrations/0002_create_groups.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.7 on 2025-11-27 23:25 + +from django.db import migrations + +ROLES = [ + "Super admin", + "Org admin", + "Senior_appraiser", + "Junior_appraiser", + "Designated_appraiser", + "Client_user", + "Client_manager", + "Client_admin", +] + +def create_groups(apps, schema_editor): + Group = apps.get_model('auth', 'Group') + for role in ROLES: + Group.objects.create(name=role) + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.RunPython(create_groups), + ] diff --git a/core/migrations/0003_property_propertyphoto.py b/core/migrations/0003_property_propertyphoto.py new file mode 100644 index 0000000..cb69c81 --- /dev/null +++ b/core/migrations/0003_property_propertyphoto.py @@ -0,0 +1,42 @@ +# Generated by Django 5.2.7 on 2025-11-27 23:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_create_groups'), + ] + + operations = [ + migrations.CreateModel( + name='Property', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('address', models.CharField(max_length=255)), + ('city', models.CharField(max_length=255)), + ('state', models.CharField(max_length=2)), + ('zip_code', models.CharField(max_length=10)), + ('property_type', models.CharField(choices=[('SINGLE_FAMILY', 'Single Family'), ('MULTI_FAMILY', 'Multi-Family'), ('COMMERCIAL', 'Commercial')], max_length=20)), + ('square_footage', models.PositiveIntegerField()), + ('bedrooms', models.PositiveIntegerField()), + ('bathrooms', models.DecimalField(decimal_places=1, max_digits=3)), + ('year_built', models.PositiveIntegerField()), + ('description', models.TextField()), + ('status', models.CharField(choices=[('FOR_SALE', 'For Sale'), ('FOR_RENT', 'For Rent'), ('SOLD', 'Sold')], max_length=20)), + ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties', to='core.organization')), + ], + ), + migrations.CreateModel( + name='PropertyPhoto', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ImageField(upload_to='property_photos/')), + ('caption', models.CharField(blank=True, max_length=255)), + ('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='photos', to='core.property')), + ], + ), + ] diff --git a/core/migrations/0004_project_appraisal_invoice.py b/core/migrations/0004_project_appraisal_invoice.py new file mode 100644 index 0000000..d3ded10 --- /dev/null +++ b/core/migrations/0004_project_appraisal_invoice.py @@ -0,0 +1,47 @@ +# Generated by Django 5.2.7 on 2025-11-28 02:14 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_property_propertyphoto'), + ] + + operations = [ + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('start_date', models.DateField()), + ('end_date', models.DateField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='Appraisal', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('appraisal_date', models.DateField()), + ('appraised_value', models.DecimalField(decimal_places=2, max_digits=12)), + ('notes', models.TextField(blank=True)), + ('appraiser', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='appraisals', to=settings.AUTH_USER_MODEL)), + ('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='appraisals', to='core.property')), + ], + ), + migrations.CreateModel( + name='Invoice', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('invoice_number', models.CharField(max_length=50)), + ('amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('due_date', models.DateField()), + ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('SENT', 'Sent'), ('PAID', 'Paid'), ('CANCELLED', 'Cancelled')], default='DRAFT', max_length=20)), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invoices', to='core.project')), + ], + ), + ] diff --git a/core/migrations/0005_user_user_role_temp.py b/core/migrations/0005_user_user_role_temp.py new file mode 100644 index 0000000..b8f291e --- /dev/null +++ b/core/migrations/0005_user_user_role_temp.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2025-11-28 04:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_project_appraisal_invoice'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='user_role_temp', + field=models.CharField(choices=[('ORG_ADMIN', 'Org admin'), ('SUPER_ADMIN', 'Super admin'), ('JUNIOR_APPRAISER', 'Junior appraiser'), ('SENIOR_APPRAISER', 'Senior appraiser'), ('DESIGNATED_APPRAISER', 'Designated appraiser'), ('CLIENT_USER', 'Client user'), ('CLIENT_MANAGER', 'Client manager'), ('CLIENT_ADMIN', 'Client admin')], default='CLIENT_USER', max_length=20), + ), + ] diff --git a/core/migrations/0006_rename_user_role_to_role.py b/core/migrations/0006_rename_user_role_to_role.py new file mode 100644 index 0000000..56f0d83 --- /dev/null +++ b/core/migrations/0006_rename_user_role_to_role.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2025-11-28 20:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_user_user_role_temp'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='user_role_temp', + new_name='role', + ), + ] \ No newline at end of file diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..bb56d59 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_create_groups.cpython-311.pyc b/core/migrations/__pycache__/0002_create_groups.cpython-311.pyc new file mode 100644 index 0000000..d6fa8d0 Binary files /dev/null and b/core/migrations/__pycache__/0002_create_groups.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_property_propertyphoto.cpython-311.pyc b/core/migrations/__pycache__/0003_property_propertyphoto.cpython-311.pyc new file mode 100644 index 0000000..ed8e16d Binary files /dev/null and b/core/migrations/__pycache__/0003_property_propertyphoto.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0004_project_appraisal_invoice.cpython-311.pyc b/core/migrations/__pycache__/0004_project_appraisal_invoice.cpython-311.pyc new file mode 100644 index 0000000..9d873be Binary files /dev/null and b/core/migrations/__pycache__/0004_project_appraisal_invoice.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0005_user_user_role_temp.cpython-311.pyc b/core/migrations/__pycache__/0005_user_user_role_temp.cpython-311.pyc new file mode 100644 index 0000000..eace857 Binary files /dev/null and b/core/migrations/__pycache__/0005_user_user_role_temp.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0006_rename_user_role_to_role.cpython-311.pyc b/core/migrations/__pycache__/0006_rename_user_role_to_role.cpython-311.pyc new file mode 100644 index 0000000..6783e3d Binary files /dev/null and b/core/migrations/__pycache__/0006_rename_user_role_to_role.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..1e272f4 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,113 @@ from django.db import models +from django.contrib.auth.models import AbstractUser -# Create your models here. +class Organization(models.Model): + name = models.CharField(max_length=255) + logo = models.ImageField(upload_to='organization_logos/', null=True, blank=True) + + def __str__(self): + return self.name + +class User(AbstractUser): + ROLE_CHOICES = [ + ('ORG_ADMIN', 'Org admin'), + ('SUPER_ADMIN', 'Super admin'), + ('JUNIOR_APPRAISER', 'Junior appraiser'), + ('SENIOR_APPRAISER', 'Senior appraiser'), + ('DESIGNATED_APPRAISER', 'Designated appraiser'), + ('CLIENT_USER', 'Client user'), + ('CLIENT_MANAGER', 'Client manager'), + ('CLIENT_ADMIN', 'Client admin'), + ] + organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='users', null=True, blank=True) + user_type = models.CharField(max_length=10, choices=[('internal', 'Internal'), ('client', 'Client')]) + role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='CLIENT_USER') + + # Add related_name to avoid clashes with default User model's groups and user_permissions + groups = models.ManyToManyField( + 'auth.Group', + verbose_name='groups', + blank=True, + help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', + related_name="core_user_set", + related_query_name="user", + ) + user_permissions = models.ManyToManyField( + 'auth.Permission', + verbose_name='user permissions', + blank=True, + help_text='Specific permissions for this user.', + related_name="core_user_set", + related_query_name="user", + ) + +class Property(models.Model): + PROPERTY_TYPE_CHOICES = [ + ('SINGLE_FAMILY', 'Single Family'), + ('MULTI_FAMILY', 'Multi-Family'), + ('COMMERCIAL', 'Commercial'), + ] + STATUS_CHOICES = [ + ('FOR_SALE', 'For Sale'), + ('FOR_RENT', 'For Rent'), + ('SOLD', 'Sold'), + ] + organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='properties') + name = models.CharField(max_length=255) + address = models.CharField(max_length=255) + city = models.CharField(max_length=255) + state = models.CharField(max_length=2) + zip_code = models.CharField(max_length=10) + property_type = models.CharField(max_length=20, choices=PROPERTY_TYPE_CHOICES) + square_footage = models.PositiveIntegerField() + bedrooms = models.PositiveIntegerField() + bathrooms = models.DecimalField(max_digits=3, decimal_places=1) + year_built = models.PositiveIntegerField() + description = models.TextField() + status = models.CharField(max_length=20, choices=STATUS_CHOICES) + + def __str__(self): + return self.name + +class PropertyPhoto(models.Model): + property = models.ForeignKey(Property, on_delete=models.CASCADE, related_name='photos') + image = models.ImageField(upload_to='property_photos/') + caption = models.CharField(max_length=255, blank=True) + + def __str__(self): + return f"Photo for {self.property.name}" + +class Appraisal(models.Model): + property = models.ForeignKey(Property, on_delete=models.CASCADE, related_name='appraisals') + appraiser = models.ForeignKey(User, on_delete=models.CASCADE, related_name='appraisals') + appraisal_date = models.DateField() + appraised_value = models.DecimalField(max_digits=12, decimal_places=2) + notes = models.TextField(blank=True) + + def __str__(self): + return f"Appraisal for {self.property.name} on {self.appraisal_date}" + +class Project(models.Model): + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + start_date = models.DateField() + end_date = models.DateField(null=True, blank=True) + + def __str__(self): + return self.name + +class Invoice(models.Model): + STATUS_CHOICES = [ + ('DRAFT', 'Draft'), + ('SENT', 'Sent'), + ('PAID', 'Paid'), + ('CANCELLED', 'Cancelled'), + ] + project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='invoices') + invoice_number = models.CharField(max_length=50) + amount = models.DecimalField(max_digits=10, decimal_places=2) + due_date = models.DateField() + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='DRAFT') + + def __str__(self): + return f"Invoice {self.invoice_number} for {self.project.name}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..768b775 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -2,24 +2,126 @@
- -Welcome, {{ user.username }}!
+| Property | +Appraiser | +Appraisal Date | +Appraised Value | +Actions | +
|---|---|---|---|---|
| {{ appraisal.property.name }} | +{{ appraisal.appraiser.username }} | +{{ appraisal.appraisal_date }} | +{{ appraisal.appraised_value }} | ++ View + | +
| Order ID | +Property Address | +Status | +Date Submitted | +
|---|---|---|---|
| {{ order.id }} | +{{ order.property_address }} | +{{ order.status }} | +{{ order.created_at|date:"Y-m-d H:i" }} | +
|
+ You have not submitted any orders yet. + |
+ |||
Please sign in to continue
+AppWizzy AI is collecting your requirements and applying the first changes.
-This page will refresh automatically as the plan is implemented.
-
- Runtime: Django {{ django_version }} · Python {{ python_version }}
- — UTC {{ current_time|date:"Y-m-d H:i:s" }}
-
| Invoice Number | +Project | +Amount | +Due Date | +Status | +Actions | +
|---|---|---|---|---|---|
| {{ invoice.invoice_number }} | +{{ invoice.project.name }} | +{{ invoice.amount }} | +{{ invoice.due_date }} | +{{ invoice.get_status_display }} | ++ View + | +
Your appraisal order has been successfully submitted.
+We will review your request shortly and be in touch. You can now safely close this page.
+ +| Name | +Description | +Start Date | +End Date | +Actions | +
|---|---|---|---|---|
| {{ project.name }} | +{{ project.description }} | +{{ project.start_date }} | +{{ project.end_date }} | ++ View + | +
| Name | +City | +Status | +Actions | +
|---|---|---|---|
| {{ property.name }} | +{{ property.city }} | +{{ property.get_status_display }} | ++ View + | +
| Username | +Role | +Actions | +|
|---|---|---|---|
| {{ user.username }} | +{{ user.email }} | +{{ user.get_role_display }} | ++ View + | +
Please sign in to continue
+Your password has been set. You may go ahead and log in now.
+Please enter your new password twice so we can verify you typed it in correctly.
+ + {% else %} +The password reset link was invalid, possibly because it has already been used. Please request a new password reset.
+ {% endif %} +We've emailed you instructions for setting your password. You should receive them shortly.
+If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder.
+Enter your email address below, and we'll email instructions for setting a new one.
+ +