Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/ad/module-badges-UI' into autost…
Browse files Browse the repository at this point in the history
…aging
  • Loading branch information
orangejenny committed Oct 30, 2024
2 parents d7af91c + 948fb21 commit 9680275
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 11 deletions.
2 changes: 2 additions & 0 deletions corehq/apps/domain/deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ def _delete_demo_user_restores(domain_name):
ModelDeletion('couchforms', 'UnfinishedSubmissionStub', 'domain'),
ModelDeletion('couchforms', 'UnfinishedArchiveStub', 'domain'),
ModelDeletion('fixtures', 'LookupTable', 'domain'),
ModelDeletion('fixtures', 'CSQLFixtureExpression', 'domain'),
ModelDeletion('fixtures', 'CSQLFixtureExpressionLog', 'expression__domain'),
CustomDeletion('ucr', delete_all_ucr_tables_for_domain, []),
ModelDeletion('domain', 'OperatorCallLimitSettings', 'domain'),
ModelDeletion('domain', 'SMSAccountConfirmationSettings', 'domain'),
Expand Down
2 changes: 2 additions & 0 deletions corehq/apps/dump_reload/sql/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@
FilteredModelIteratorBuilder('fixtures.LookupTable', SimpleFilter('domain')),
FilteredModelIteratorBuilder('fixtures.LookupTableRow', SimpleFilter('domain')),
FilteredModelIteratorBuilder('fixtures.LookupTableRowOwner', SimpleFilter('domain')),
FilteredModelIteratorBuilder('fixtures.CSQLFixtureExpression', SimpleFilter('domain')),
FilteredModelIteratorBuilder('fixtures.CSQLFixtureExpressionLog', SimpleFilter('expression__domain')),
FilteredModelIteratorBuilder('hqmedia.LogoForSystemEmailsReference', SimpleFilter('domain')),
FilteredModelIteratorBuilder('userreports.UCRExpression', SimpleFilter('domain')),
FilteredModelIteratorBuilder('generic_inbound.ConfigurableAPI', SimpleFilter('domain')),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 4.2.16 on 2024-10-29 18:29

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('fixtures', '0010_lookuptable_is_synced'),
]

operations = [
migrations.CreateModel(
name='CSQLFixtureExpression',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('domain', models.CharField(default='', max_length=64)),
('name', models.CharField(default='', max_length=64)),
('csql', models.CharField(default='', max_length=2000)),
('date_created', models.DateTimeField(auto_now_add=True)),
('last_modified', models.DateTimeField(auto_now=True)),
('deleted', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='CSQLFixtureExpressionLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True)),
('action', models.CharField(choices=[('update', 'Updated'), ('create', 'Created'), ('delete', 'Deleted')], max_length=16)),
('name', models.CharField(default='', max_length=64)),
('csql', models.CharField(default='', max_length=2000)),
('expression', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='fixtures.csqlfixtureexpression')),
],
),
]
56 changes: 55 additions & 1 deletion corehq/apps/fixtures/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from datetime import datetime
from django.db.models import ForeignKey
from functools import reduce
from itertools import chain
from uuid import uuid4

from attrs import define, field
from django.db import models
from django.db.models.expressions import RawSQL
from django.db.models.signals import post_save
from django.db.transaction import atomic
from django.dispatch import receiver
from django.utils.translation import gettext as _

from corehq.apps.groups.models import Group
from corehq.sql_db.fields import CharIdField
from corehq.util.jsonattrs import AttrsDict, AttrsList, list_of

from .exceptions import FixtureVersionError

FIXTURE_BUCKET = 'domain-fixtures'
Expand Down Expand Up @@ -306,3 +310,53 @@ class Meta:
app_label = 'fixtures'
db_table = 'fixtures_userfixturestatus'
unique_together = ("user_id", "fixture_type")


class CSQLFixtureExpression(models.Model):
domain = models.CharField(max_length=64, default='')
name = models.CharField(max_length=64, default='')
csql = models.CharField(max_length=2000, default='')
date_created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
deleted = models.BooleanField(default=False)

@classmethod
def by_domain(cls, domain):
return cls.objects.filter(domain=domain, deleted=False)

@atomic
def soft_delete(self):
self.deleted = True
self.save()
CSQLFixtureExpressionLog.objects.create(
expression=self,
action=CSQLFixtureExpressionLog.DELETE,
)


class CSQLFixtureExpressionLog(models.Model):

CREATE = 'create'
DELETE = 'delete'
UPDATE = 'update'

expression = ForeignKey(CSQLFixtureExpression, on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True)
action = models.CharField(max_length=16, choices=(
(UPDATE, _('Updated')),
(CREATE, _('Created')),
(DELETE, _('Deleted')),
), null=False)
name = models.CharField(max_length=64, default='')
csql = models.CharField(max_length=2000, default='')


@receiver(post_save, sender=CSQLFixtureExpression)
def after_save(sender, instance, created, **kwargs):
updated_or_created = CSQLFixtureExpressionLog.CREATE if created else CSQLFixtureExpressionLog.UPDATE
CSQLFixtureExpressionLog.objects.create(
expression=instance,
action=updated_or_created,
name=instance.name,
csql=instance.csql
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{% extends 'hqwebapp/bootstrap5/base_section.html' %}
{% load hq_shared_tags %}
{% load i18n %}

{% js_entry_b3 "hqwebapp/js/htmx_and_alpine" %}

{% block page_title %}
{% trans 'CSQL Fixture Configuration' %}
{% endblock %}

{% block page_content %}
<div x-data="{
validationFailed: false,
isDirty: false,
rows: {{ csql_fixture_configurations|safe }},
addRow() {
this.rows.push({ row: '', csql: '', id: '' });
},
deleteRow(index) {
this.rows.splice(index, 1);
this.markDirty();
},
markDirty() {
this.isDirty = true;
},
unmarkDirty() {
this.isDirty = false;
}
}">
<form hx-post={{ save_url }} action="/submit" method="POST" hx-target="#response-message" hx-swap="innerHTML"
@change="markDirty" @submit="unmarkDirty">
<div id="response-message"></div>
<button type="submit" class="btn btn-primary" id="Save" x-bind:disabled="!isDirty">{% trans "Save" %}</button>
<p/>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="col-sm-2">
{% trans "Name" %}
</th>
<th class="col-sm-7">
{% trans "Case Search QL Expression" %}
</th>
<th class="col-sm-1">
{% trans "Delete" %}
</th>
</thead>
<tbody>
<template x-for="(row, index) in rows" :key="index">
<tr>
<td><input class="form-control" type="text" name="name" x-model="row.name" maxlength="64"></td>
<td><textarea class="form-control vertical-resize" spellcheck="false" rows="1" name="csql"
x-model="row.csql" maxlength="2000"></textarea></td>
<td><button class="btn btn-danger"
@click="deleteRow(index)">
<i class="fa-regular fa-trash-can"></i>
{% trans "Delete" %}
</button></td>
<input type="hidden" name="id" x-model="row.id">
</tr>
</template>
</tbody>
</table>
<button type="button" class="btn btn-default" @click="addRow()">
<i class="fa fa-plus"></i>
{% trans "Add Indicator" %}
</button>
<br>
</form>
</div>
{% endblock %}
59 changes: 56 additions & 3 deletions corehq/apps/fixtures/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
from corehq.apps.domain.models import Domain

from corehq.apps.domain.shortcuts import create_domain
from corehq.apps.users.models import WebUser

from corehq.util.test_utils import flag_enabled
from corehq.apps.fixtures.models import CSQLFixtureExpression, CSQLFixtureExpressionLog
from corehq.apps.fixtures.views import CSQLFixtureExpressionView, update_tables
from corehq.apps.users.dbaccessors import delete_all_users
from corehq.apps.users.models import HqPermissions, WebUser
from corehq.apps.users.models_role import UserRole
from ..interface import FixtureEditInterface
from ..models import LookupTable, LookupTableRow, Field, TypeField
from corehq.apps.fixtures.views import update_tables

DOMAIN = "lookup"
USER = "[email protected]"
Expand Down Expand Up @@ -316,3 +319,53 @@ def _create_request(self, method):
request.user = request.couch_user = WebUser(is_superuser=True, is_authenticated=True, is_active=True)

return request


@flag_enabled('MODULE_BADGES')
class TestCSQLFixtureExpressionView(TestCase):

DOMAIN = 'test-domain'
DEFAULT_USER_PASSWORD = 'password'
USERNAME = '[email protected]'

@classmethod
def setUpClass(cls):
super(TestCSQLFixtureExpressionView, cls).setUpClass()
cls.domain_obj = create_domain(cls.DOMAIN)
cls.user = WebUser.create(
cls.DOMAIN, cls.USERNAME, cls.DEFAULT_USER_PASSWORD, None, None, is_admin=True
)
cls.role = UserRole.create(cls.DOMAIN, 'Fixtures Access', HqPermissions(edit_data=True))
cls.user.set_role(cls.DOMAIN, cls.role.get_qualified_id())

@classmethod
def tearDownClass(cls):
cls.user.delete(cls.DOMAIN, deleted_by=None)
cls.domain_obj.delete()
delete_all_users()
super(TestCSQLFixtureExpressionView, cls).tearDownClass()

def _get_view_response(self, data):
self.client.login(username=self.USERNAME, password=self.DEFAULT_USER_PASSWORD)
response = self.client.post(reverse(CSQLFixtureExpressionView.urlname, args=[self.DOMAIN]), data)
return response

@patch('django_prbac.decorators.has_privilege', return_value=True)
def test_create_update_delete(self, has_privilege):
deleted_exp = CSQLFixtureExpression.objects.create(domain=self.DOMAIN, name='deleted_exp', csql='asdf')
exp2 = CSQLFixtureExpression.objects.create(domain=self.DOMAIN, name='exp2', csql='asdf')
data = {
'id': [exp2.id, ''],
'name': ['exp2', 'exp3'],
'csql': ['asdfg', 'asdf'],
}
response = self._get_view_response(data)
self.assertEqual(response.status_code, 200)
self.assertFalse(CSQLFixtureExpression.objects.filter(
domain=self.DOMAIN, name='deleted_exp', deleted=False).exists())
CSQLFixtureExpression.objects.get(domain=self.DOMAIN, name='exp2', id=exp2.id, csql='asdfg')
CSQLFixtureExpression.objects.get(domain=self.DOMAIN, name='exp3', csql='asdf')
CSQLFixtureExpressionLog.objects.get(expression=deleted_exp,
action=CSQLFixtureExpressionLog.DELETE)
CSQLFixtureExpressionLog.objects.get(expression=exp2, action=CSQLFixtureExpressionLog.UPDATE)
CSQLFixtureExpressionLog.objects.get(name='exp3', action=CSQLFixtureExpressionLog.CREATE)
3 changes: 3 additions & 0 deletions corehq/apps/fixtures/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
fixture_upload_job_poll,
update_tables,
upload_fixture_api,
CSQLFixtureExpressionView
)
from corehq.apps.hqwebapp.decorators import waf_allow

Expand All @@ -25,6 +26,8 @@
url(r'^edit_lookup_tables/upload/$', waf_allow('XSS_BODY')(UploadItemLists.as_view()), name='upload_fixtures'),
url(r'^edit_lookup_tables/update-tables/(?P<data_type_id>[\w-]+)?$', update_tables,
name='update_lookup_tables'),
url(r'^csql_fixture/csql_fixture_configuration/$', CSQLFixtureExpressionView.as_view(),
name=CSQLFixtureExpressionView.urlname),

# upload status
url(r'^upload/status/(?P<download_id>(?:dl-)?[0-9a-fA-Z]{25,32})/$',
Expand Down
Loading

0 comments on commit 9680275

Please sign in to comment.