diff --git a/README.md b/README.md index 4e78fe1..50b004f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,9 @@ Moved to [settings](http://cookiecutter-django.readthedocs.io/en/latest/settings - To create a **superuser account**, use this command: - $ python manage.py createsuperuser +```bash + python manage.py createsuperuser +``` For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users. @@ -27,19 +29,35 @@ For convenience, you can keep your normal user logged in on Chrome and your supe Running type checks with mypy: - $ mypy yeastregulatorydb +```bash + mypy yeastregulatorydb +``` + +#### mypy caveats + +- there is an issue accessing `Models.objects` that is unresolved. See + [issue 1684](https://github.com/typeddjango/django-stubs/issues/1684). I + choose to resolve this by ignoring the attr-defined error when accessing + `Model.objects` (see any viewset for an example). Note that in cases where + a custom manager is defined, I use _default_manager + ([see django docs](https://docs.djangoproject.com/en/4.2/topics/db/managers/#django.db.models.Model._default_manager)). This raises a "accessing private method" warning in pylint, + which is also ignored. ### Test coverage To run the tests, check your test coverage, and generate an HTML coverage report: - $ coverage run -m pytest - $ coverage html - $ open htmlcov/index.html +```bash + coverage run -m pytest + coverage html + open htmlcov/index.html +``` #### Running tests with pytest - $ pytest +```bash + pytest +``` ### Live reloading and Sass CSS compilation @@ -161,4 +179,4 @@ it okay to delete and re-download it? [y/n] (y): y Choose from [1/2/3/4/5] (1): 4 [26/27] keep_local_envs_in_vcs (y): n [27/27] debug (n): n -``` \ No newline at end of file +``` diff --git a/compose/local/django/Dockerfile b/compose/local/django/Dockerfile index 4b8946f..d02a9f4 100644 --- a/compose/local/django/Dockerfile +++ b/compose/local/django/Dockerfile @@ -11,7 +11,11 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ # dependencies for building Python packages build-essential \ # psycopg2 dependencies - libpq-dev + libpq-dev \ + # HTSlib dependencies + zlib1g-dev \ + libbz2-dev \ + liblzma-dev # Requirements are installed here to ensure they will be cached. COPY ./requirements . diff --git a/compose/local/docs/Dockerfile b/compose/local/docs/Dockerfile index 37e3baa..0d670b4 100644 --- a/compose/local/docs/Dockerfile +++ b/compose/local/docs/Dockerfile @@ -12,6 +12,10 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ build-essential \ # psycopg2 dependencies libpq-dev \ + # HTSlib dependencies + zlib1g-dev \ + libbz2-dev \ + liblzma-dev \ # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* diff --git a/compose/production/django/Dockerfile b/compose/production/django/Dockerfile index 76549c8..0f0b14c 100644 --- a/compose/production/django/Dockerfile +++ b/compose/production/django/Dockerfile @@ -12,7 +12,11 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ # dependencies for building Python packages build-essential \ # psycopg2 dependencies - libpq-dev + libpq-dev \ + # HTSlib dependencies + zlib1g-dev \ + libbz2-dev \ + liblzma-dev # Requirements are installed here to ensure they will be cached. COPY ./requirements . diff --git a/config/api_router.py b/config/api_router.py index 9134400..ab508fa 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -1,6 +1,20 @@ from django.conf import settings from rest_framework.routers import DefaultRouter, SimpleRouter +from yeastregulatorydb.regulatory_data.api.views import ( + BindingSourceViewSet, + BindingViewSet, + CallingCardsBackgroundViewSet, + ChrMapViewSet, + ExpressionManualQCViewSet, + ExpressionSourceViewSet, + ExpressionViewSet, + FileFormatViewSet, + GenomicFeatureViewSet, + PromoterSetSigViewSet, + PromoterSetViewSet, + RegulatorViewSet, +) from yeastregulatorydb.users.api.views import UserViewSet if settings.DEBUG: @@ -9,6 +23,18 @@ router = SimpleRouter() router.register("users", UserViewSet) +router.register("bindingsource", BindingSourceViewSet) +router.register("binding", BindingViewSet) +router.register("callingcardsbackground", CallingCardsBackgroundViewSet) +router.register("chrmap", ChrMapViewSet) +router.register("expressionmanualqc", ExpressionManualQCViewSet) +router.register("expressionsource", ExpressionSourceViewSet) +router.register("expression", ExpressionViewSet) +router.register("fileformat", FileFormatViewSet) +router.register("genomicfeature", GenomicFeatureViewSet) +router.register("promotersetsig", PromoterSetSigViewSet) +router.register("promoterset", PromoterSetViewSet) +router.register("regulator", RegulatorViewSet) app_name = "api" diff --git a/docs/api/yeastregulatorydb.regulatory_data.api.serializers.rst b/docs/api/yeastregulatorydb.regulatory_data.api.serializers.rst index 81ba03f..b8ab7a5 100644 --- a/docs/api/yeastregulatorydb.regulatory_data.api.serializers.rst +++ b/docs/api/yeastregulatorydb.regulatory_data.api.serializers.rst @@ -100,10 +100,10 @@ yeastregulatorydb.regulatory\_data.api.serializers.PromoterSetSigSerializer modu :undoc-members: :show-inheritance: -yeastregulatorydb.regulatory\_data.api.serializers.RegulatorSerializerSerializer module +yeastregulatorydb.regulatory\_data.api.serializers.RegulatorSerializer module --------------------------------------------------------------------------------------- -.. automodule:: yeastregulatorydb.regulatory_data.api.serializers.RegulatorSerializerSerializer +.. automodule:: yeastregulatorydb.regulatory_data.api.serializers.RegulatorSerializer :members: :undoc-members: :show-inheritance: diff --git a/docs/index.rst b/docs/index.rst index 0960a83..cb5241d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,7 +12,6 @@ Welcome to YeastRegulatoryDB's documentation! howto users - api diff --git a/docs/readthedocs.db b/docs/readthedocs.db new file mode 100644 index 0000000..e69de29 diff --git a/requirements/base.txt b/requirements/base.txt index 86030ed..3b34776 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -24,3 +24,7 @@ django-cors-headers==4.3.1 # https://github.com/adamchainz/django-cors-headers # DRF-spectacular for api documentation drf-spectacular==0.26.5 # https://github.com/tfranzel/drf-spectacular django-filter==23.5 # https://github.com/carltongibson/django-filter + +# Third Party Software +# ------------------------------------------------------------------------------ +callingcardstools==1.3.0 # https://github.com/cmatkhan/callingcardstools diff --git a/yeastregulatorydb/contrib/sites/migrations/0001_initial.py b/yeastregulatorydb/contrib/sites/migrations/0001_initial.py index 304cd6d..59647c8 100644 --- a/yeastregulatorydb/contrib/sites/migrations/0001_initial.py +++ b/yeastregulatorydb/contrib/sites/migrations/0001_initial.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [] operations = [ diff --git a/yeastregulatorydb/contrib/sites/migrations/0002_alter_domain_unique.py b/yeastregulatorydb/contrib/sites/migrations/0002_alter_domain_unique.py index 2c8d6da..4359049 100644 --- a/yeastregulatorydb/contrib/sites/migrations/0002_alter_domain_unique.py +++ b/yeastregulatorydb/contrib/sites/migrations/0002_alter_domain_unique.py @@ -3,7 +3,6 @@ class Migration(migrations.Migration): - dependencies = [("sites", "0001_initial")] operations = [ diff --git a/yeastregulatorydb/contrib/sites/migrations/0003_set_site_domain_and_name.py b/yeastregulatorydb/contrib/sites/migrations/0003_set_site_domain_and_name.py index e3afa70..d6c9906 100644 --- a/yeastregulatorydb/contrib/sites/migrations/0003_set_site_domain_and_name.py +++ b/yeastregulatorydb/contrib/sites/migrations/0003_set_site_domain_and_name.py @@ -23,7 +23,7 @@ def _update_or_create_site_with_sequence(site_model, connection, domain, name): # site is created. # To avoid this, we need to manually update DB sequence and make sure it's # greater than the maximum value. - max_id = site_model.objects.order_by('-id').first().id + max_id = site_model.objects.order_by("-id").first().id with connection.cursor() as cursor: cursor.execute("SELECT last_value from django_site_id_seq") (current_id,) = cursor.fetchone() @@ -57,7 +57,6 @@ def update_site_backward(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [("sites", "0002_alter_domain_unique")] operations = [migrations.RunPython(update_site_forward, update_site_backward)] diff --git a/yeastregulatorydb/contrib/sites/migrations/0004_alter_options_ordering_domain.py b/yeastregulatorydb/contrib/sites/migrations/0004_alter_options_ordering_domain.py index f7118ca..095ca00 100644 --- a/yeastregulatorydb/contrib/sites/migrations/0004_alter_options_ordering_domain.py +++ b/yeastregulatorydb/contrib/sites/migrations/0004_alter_options_ordering_domain.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("sites", "0003_set_site_domain_and_name"), ] diff --git a/yeastregulatorydb/regulatory_data/admin.py b/yeastregulatorydb/regulatory_data/admin.py index 8c38f3f..d9edb9a 100644 --- a/yeastregulatorydb/regulatory_data/admin.py +++ b/yeastregulatorydb/regulatory_data/admin.py @@ -1,3 +1,5 @@ +# pylint: disable=unused-import from django.contrib import admin # Register your models here. +from .models import * diff --git a/yeastregulatorydb/regulatory_data/api/filters/BindingFilter.py b/yeastregulatorydb/regulatory_data/api/filters/BindingFilter.py index fbb43ad..5b48d5c 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/BindingFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/BindingFilter.py @@ -4,12 +4,14 @@ class BindingFilter(django_filters.FilterSet): + # pylint: disable=R0801 id = django_filters.NumberFilter() pk = django_filters.NumberFilter() regulator_id = django_filters.NumberFilter() regulator_locus_tag = django_filters.CharFilter(field_name="regulator__locus_tag", lookup_expr="iexact") regulator_symbol = django_filters.CharFilter(field_name="regulator__symbol", lookup_expr="iexact") batch = django_filters.CharFilter(lookup_expr="iexact") + # pylint: enable=R0801 replicate = django_filters.NumberFilter() source_id = django_filters.NumberFilter() source_orig_id = django_filters.CharFilter(lookup_expr="iexact") @@ -17,6 +19,7 @@ class BindingFilter(django_filters.FilterSet): assay = django_filters.CharFilter(field_name="source_id__assay", lookup_expr="iexact") workflow = django_filters.CharFilter(field_name="source_id__workflow", lookup_expr="iexact") + # pylint: disable=R0801 class Meta: model = Binding fields = [ @@ -33,3 +36,5 @@ class Meta: "assay", "workflow", ] + + # pylint: enable=R0801 diff --git a/yeastregulatorydb/regulatory_data/api/filters/CallingCardsBackgroundFilter.py b/yeastregulatorydb/regulatory_data/api/filters/CallingCardsBackgroundFilter.py index 3dfdd00..2378c27 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/CallingCardsBackgroundFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/CallingCardsBackgroundFilter.py @@ -3,7 +3,7 @@ from ...models.CallingCardsBackground import CallingCardsBackground -class CallingCardsBackgroundFilter(django_filters.filters.FilterSet): +class CallingCardsBackgroundFilter(django_filters.FilterSet): class Meta: model = CallingCardsBackground fields = {"name": ["exact"]} diff --git a/yeastregulatorydb/regulatory_data/api/filters/ExpressionFilter.py b/yeastregulatorydb/regulatory_data/api/filters/ExpressionFilter.py index 7523468..b76f3a3 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/ExpressionFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/ExpressionFilter.py @@ -4,12 +4,14 @@ class ExpressionFilter(django_filters.FilterSet): + # pylint: disable=R0801 id = django_filters.NumberFilter() pk = django_filters.NumberFilter() regulator_id = django_filters.NumberFilter() regulator_locus_tag = django_filters.CharFilter(field_name="regulator__locus_tag", lookup_expr="iexact") regulator_symbol = django_filters.CharFilter(field_name="regulator__symbol", lookup_expr="iexact") batch = django_filters.CharFilter(field_name="batch", lookup_expr="iexact") + # pylint: enable=R0801 replicate = django_filters.NumberFilter() control = django_filters.CharFilter(lookup_expr="iexact") mechanism = django_filters.CharFilter(lookup_expr="iexact") @@ -39,3 +41,5 @@ class Meta: "assay", "workflow", ] + + # pylint: enable=R0801 diff --git a/yeastregulatorydb/regulatory_data/api/filters/ExpressionManualQCFilter.py b/yeastregulatorydb/regulatory_data/api/filters/ExpressionManualQCFilter.py index a115a2e..c40f116 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/ExpressionManualQCFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/ExpressionManualQCFilter.py @@ -23,6 +23,7 @@ class ExpressionManualQCFilter(django_filters.FilterSet): assay = django_filters.CharFilter(field_name="expression_id__source_id__assay", lookup_expr="iexact") workflow = django_filters.CharFilter(field_name="expression_id__source_id__workflow", lookup_expr="iexact") + # pylint: disable=R0801 class Meta: model = ExpressionManualQC fields = [ @@ -43,3 +44,5 @@ class Meta: "assay", "workflow", ] + + # pylint: enable=R0801 diff --git a/yeastregulatorydb/regulatory_data/api/filters/FileFormatFilter.py b/yeastregulatorydb/regulatory_data/api/filters/FileFormatFilter.py index 1a4a552..6440f2f 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/FileFormatFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/FileFormatFilter.py @@ -3,7 +3,7 @@ from ...models.FileFormat import FileFormat -class ExpressionManualQCFilter(django_filters.FilterSet): +class FileFormatFilter(django_filters.FilterSet): class Meta: model = FileFormat fields = {"fileformat": ["exact"]} diff --git a/yeastregulatorydb/regulatory_data/api/filters/GenomicFeatureFilter.py b/yeastregulatorydb/regulatory_data/api/filters/GenomicFeatureFilter.py index 2a248da..3a18467 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/GenomicFeatureFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/GenomicFeatureFilter.py @@ -7,7 +7,7 @@ class GenomicFeatureFilter(django_filters.FilterSet): chr = django_filters.CharFilter(field_name="chr__ucsc") start = django_filters.NumberFilter() end = django_filters.NumberFilter() - strand = django_filters.ChoiceFilter(choices=GenomicFeature.STRAND_CHOICES) + strand = django_filters.CharFilter(lookup_expr='exact') type = django_filters.CharFilter(lookup_expr="iexact") gene_biotype = django_filters.CharFilter(lookup_expr="iexact") locus_tag = django_filters.CharFilter(lookup_expr="iexact") diff --git a/yeastregulatorydb/regulatory_data/api/filters/PromoterSetSigFilter.py b/yeastregulatorydb/regulatory_data/api/filters/PromoterSetSigFilter.py index c7ce820..94e6e9f 100644 --- a/yeastregulatorydb/regulatory_data/api/filters/PromoterSetSigFilter.py +++ b/yeastregulatorydb/regulatory_data/api/filters/PromoterSetSigFilter.py @@ -22,6 +22,7 @@ class PromoterSetSigFilter(django_filters.FilterSet): assay = django_filters.CharFilter(field_name="binding_id__source_id__assay", lookup_expr="iexact") workflow = django_filters.CharFilter(field_name="binding_id__source_id__workflow", lookup_expr="iexact") + # pylint: disable=R0801 class Meta: model = PromoterSetSig fields = [ @@ -39,3 +40,5 @@ class Meta: "assay", "workflow", ] + + # pylint: enable=R0801 diff --git a/yeastregulatorydb/regulatory_data/api/serializers/BindingManualQCSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/BindingManualQCSerializer.py index fcf8730..154f4f9 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/BindingManualQCSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/BindingManualQCSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.BindingManualQC import BindingManualQC +from .mixins.CustomValidateMixin import CustomValidateMixin -class BindingManualQCSerializer(serializers.ModelSerializer): +class BindingManualQCSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/BindingSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/BindingSerializer.py index 46d52d7..19af5a0 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/BindingSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/BindingSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.Binding import Binding +from .mixins.CustomValidateMixin import CustomValidateMixin -class BindingSerializer(serializers.ModelSerializer): +class BindingSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/BindingSourceSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/BindingSourceSerializer.py index ef40382..bd81c15 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/BindingSourceSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/BindingSourceSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.BindingSource import BindingSource +from .mixins.CustomValidateMixin import CustomValidateMixin -class BindingSourceSerializer(serializers.ModelSerializer): +class BindingSourceSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/CallingCardsBackgroundSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/CallingCardsBackgroundSerializer.py index 4ed8216..53e3b76 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/CallingCardsBackgroundSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/CallingCardsBackgroundSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.CallingCardsBackground import CallingCardsBackground +from .mixins.CustomValidateMixin import CustomValidateMixin -class CallingCardsBackgroundSerializer(serializers.ModelSerializer): +class CallingCardsBackgroundSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/ChrMapSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/ChrMapSerializer.py index 5c68bc1..dbd6990 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/ChrMapSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/ChrMapSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.ChrMap import ChrMap +from .mixins.CustomValidateMixin import CustomValidateMixin -class ChrMapSerializer(serializers.ModelSerializer): +class ChrMapSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/ExpressionManualQCSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/ExpressionManualQCSerializer.py index 89fc45c..954a447 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/ExpressionManualQCSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/ExpressionManualQCSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.ExpressionManualQC import ExpressionManualQC +from .mixins.CustomValidateMixin import CustomValidateMixin -class ExpressionManualQCSerializer(serializers.ModelSerializer): +class ExpressionManualQCSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSerializer.py index c097007..6b6217f 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.Expression import Expression +from .mixins.CustomValidateMixin import CustomValidateMixin -class ExpressionSerializer(serializers.ModelSerializer): +class ExpressionSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSourceSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSourceSerializer.py index 2be2544..dffb3b1 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSourceSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/ExpressionSourceSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.ExpressionSource import ExpressionSource +from .mixins.CustomValidateMixin import CustomValidateMixin -class ExpressionSourceSerializer(serializers.ModelSerializer): +class ExpressionSourceSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/FileFormatSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/FileFormatSerializer.py index 4323db3..f1e207b 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/FileFormatSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/FileFormatSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.FileFormat import FileFormat +from .mixins.CustomValidateMixin import CustomValidateMixin -class FileFormatSerializer(serializers.ModelSerializer): +class FileFormatSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/GenomicFeatureSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/GenomicFeatureSerializer.py index 66497ac..1bab0b6 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/GenomicFeatureSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/GenomicFeatureSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.GenomicFeature import GenomicFeature +from .mixins.CustomValidateMixin import CustomValidateMixin -class GenomicFeatureSerializer(serializers.ModelSerializer): +class GenomicFeatureSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSerializer.py index 4b06905..a1b6950 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.PromoterSet import PromoterSet +from .mixins.CustomValidateMixin import CustomValidateMixin -class PromoterSetSerializer(serializers.ModelSerializer): +class PromoterSetSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSigSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSigSerializer.py index c1b5e5c..1b205d1 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSigSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/PromoterSetSigSerializer.py @@ -1,12 +1,16 @@ from rest_framework import serializers from ...models.PromoterSetSig import PromoterSetSig +from .mixins.CustomValidateMixin import CustomValidateMixin -class PromoterSetSigSerializer(serializers.ModelSerializer): +class PromoterSetSigSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) class Meta: model = PromoterSetSig fields = "__all__" + + def get_background_id(self, obj): + return obj.background_id.id if obj.background_id else "undefined" diff --git a/yeastregulatorydb/regulatory_data/api/serializers/RegulatorSerializerSerializer.py b/yeastregulatorydb/regulatory_data/api/serializers/RegulatorSerializer.py similarity index 69% rename from yeastregulatorydb/regulatory_data/api/serializers/RegulatorSerializerSerializer.py rename to yeastregulatorydb/regulatory_data/api/serializers/RegulatorSerializer.py index c819883..31833cf 100644 --- a/yeastregulatorydb/regulatory_data/api/serializers/RegulatorSerializerSerializer.py +++ b/yeastregulatorydb/regulatory_data/api/serializers/RegulatorSerializer.py @@ -1,9 +1,10 @@ from rest_framework import serializers from ...models.Regulator import Regulator +from .mixins.CustomValidateMixin import CustomValidateMixin -class RegulatorSerializer(serializers.ModelSerializer): +class RegulatorSerializer(CustomValidateMixin, serializers.ModelSerializer): uploader = serializers.ReadOnlyField(source="uploader.username") modifiedBy = serializers.CharField(source="uploader.username", required=False) diff --git a/yeastregulatorydb/regulatory_data/api/serializers/mixins/CustomValidateMixin.py b/yeastregulatorydb/regulatory_data/api/serializers/mixins/CustomValidateMixin.py new file mode 100644 index 0000000..85fcbaf --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/serializers/mixins/CustomValidateMixin.py @@ -0,0 +1,33 @@ +from rest_framework import serializers + + +class CustomValidateMixin: # pylint: disable=too-few-public-methods + """ + A mixin for Django Rest Framework serializers that raises a 400 error if + the user tries to pass `modifiedBy` in the request data. `modifiedBy` is + set automatically in the view or viewset. + + To use this mixin, include it in your serializer and call `super()` in your + `validate()` method. + + Example: + + .. code-block:: python + + class YourSerializer(ValidateModifiedByMixin, + serializers.ModelSerializer): + ... + + def validate(self, data): + if 'modifiedBy' in data: + raise serializers.ValidationError( + {'modifiedBy': 'This field is read-only.'}) + + return super().validate(data) + """ + + def validate(self, data): + if "modifiedBy" in data: + raise serializers.ValidationError({"modifiedBy": "This field is read-only."}) + + return super().validate(data) # type: ignore[misc] diff --git a/yeastregulatorydb/regulatory_data/api/serializers/mixins/__init__.py b/yeastregulatorydb/regulatory_data/api/serializers/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yeastregulatorydb/regulatory_data/api/views/BindingSourceViewSet.py b/yeastregulatorydb/regulatory_data/api/views/BindingSourceViewSet.py new file mode 100644 index 0000000..48a9616 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/BindingSourceViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.BindingSource import BindingSource +from ..filters.BindingSourceFilter import BindingSourceFilter +from ..serializers.BindingSourceSerializer import BindingSourceSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class BindingSourceViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing BindingSource instances. + """ + + queryset = BindingSource.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = BindingSourceSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = BindingSourceFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/BindingViewSet.py b/yeastregulatorydb/regulatory_data/api/views/BindingViewSet.py new file mode 100644 index 0000000..948d333 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/BindingViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.Binding import Binding +from ..filters.BindingFilter import BindingFilter +from ..serializers.BindingSerializer import BindingSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class BindingViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing Binding instances. + """ + + queryset = Binding.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = BindingSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = BindingFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/CallingCardsBackgroundViewSet.py b/yeastregulatorydb/regulatory_data/api/views/CallingCardsBackgroundViewSet.py new file mode 100644 index 0000000..edd6abe --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/CallingCardsBackgroundViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.CallingCardsBackground import CallingCardsBackground +from ..filters.CallingCardsBackgroundFilter import CallingCardsBackgroundFilter +from ..serializers.CallingCardsBackgroundSerializer import CallingCardsBackgroundSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class CallingCardsBackgroundViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing CallingCardsBackground instances. + """ + + queryset = CallingCardsBackground.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = CallingCardsBackgroundSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = CallingCardsBackgroundFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/ChrMapViewSet.py b/yeastregulatorydb/regulatory_data/api/views/ChrMapViewSet.py new file mode 100644 index 0000000..5122c6a --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/ChrMapViewSet.py @@ -0,0 +1,20 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.ChrMap import ChrMap +from ..serializers.ChrMapSerializer import ChrMapSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class ChrMapViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing ChrMap instances. + """ + + queryset = ChrMap.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = ChrMapSerializer + filter_backends = [DjangoFilterBackend] diff --git a/yeastregulatorydb/regulatory_data/api/views/ExpressionManualQCViewSet.py b/yeastregulatorydb/regulatory_data/api/views/ExpressionManualQCViewSet.py new file mode 100644 index 0000000..6c35d70 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/ExpressionManualQCViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.ExpressionManualQC import ExpressionManualQC +from ..filters.ExpressionManualQCFilter import ExpressionManualQCFilter +from ..serializers.ExpressionManualQCSerializer import ExpressionManualQCSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class ExpressionManualQCViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing ExpressionManualQC instances. + """ + + queryset = ExpressionManualQC.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = ExpressionManualQCSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = ExpressionManualQCFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/ExpressionSourceViewSet.py b/yeastregulatorydb/regulatory_data/api/views/ExpressionSourceViewSet.py new file mode 100644 index 0000000..a491efe --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/ExpressionSourceViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.ExpressionSource import ExpressionSource +from ..filters.ExpressionSourceFilter import ExpressionSourceFilter +from ..serializers.ExpressionSourceSerializer import ExpressionSourceSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class ExpressionSourceViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing ExpressionSource instances. + """ + + queryset = ExpressionSource.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = ExpressionSourceSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = ExpressionSourceFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/ExpressionViewSet.py b/yeastregulatorydb/regulatory_data/api/views/ExpressionViewSet.py new file mode 100644 index 0000000..9404fe6 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/ExpressionViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.Expression import Expression +from ..filters.ExpressionFilter import ExpressionFilter +from ..serializers.ExpressionSerializer import ExpressionSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class ExpressionViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing Expression instances. + """ + + queryset = Expression.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = ExpressionSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = ExpressionFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/FileFormatViewSet.py b/yeastregulatorydb/regulatory_data/api/views/FileFormatViewSet.py new file mode 100644 index 0000000..1fe9cbc --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/FileFormatViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.FileFormat import FileFormat +from ..filters.FileFormatFilter import FileFormatFilter +from ..serializers.FileFormatSerializer import FileFormatSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class FileFormatViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing FileFormat instances. + """ + + queryset = FileFormat.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = FileFormatSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = FileFormatFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/GenomicFeatureViewSet.py b/yeastregulatorydb/regulatory_data/api/views/GenomicFeatureViewSet.py new file mode 100644 index 0000000..1d7fc83 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/GenomicFeatureViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.GenomicFeature import GenomicFeature +from ..filters.GenomicFeatureFilter import GenomicFeatureFilter +from ..serializers.GenomicFeatureSerializer import GenomicFeatureSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class GenomicFeatureViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing GenomicFeature instances. + """ + + queryset = GenomicFeature.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = GenomicFeatureSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = GenomicFeatureFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/PromoterSetSigViewSet.py b/yeastregulatorydb/regulatory_data/api/views/PromoterSetSigViewSet.py new file mode 100644 index 0000000..19d70a8 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/PromoterSetSigViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.PromoterSetSig import PromoterSetSig +from ..filters.PromoterSetSigFilter import PromoterSetSigFilter +from ..serializers.PromoterSetSigSerializer import PromoterSetSigSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class PromoterSetSigViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing PromoterSetSig instances. + """ + + queryset = PromoterSetSig.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = PromoterSetSigSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = PromoterSetSigFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/PromoterSetViewSet.py b/yeastregulatorydb/regulatory_data/api/views/PromoterSetViewSet.py new file mode 100644 index 0000000..c7e0c9f --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/PromoterSetViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.PromoterSet import PromoterSet +from ..filters.PromoterSetFilter import PromoterSetFilter +from ..serializers.PromoterSetSerializer import PromoterSetSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class PromoterSetViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing PromoterSet instances. + """ + + queryset = PromoterSet.objects.all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = PromoterSetSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = PromoterSetFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/RegulatorViewSet.py b/yeastregulatorydb/regulatory_data/api/views/RegulatorViewSet.py new file mode 100644 index 0000000..0829160 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/RegulatorViewSet.py @@ -0,0 +1,22 @@ +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import viewsets +from rest_framework.authentication import SessionAuthentication, TokenAuthentication +from rest_framework.permissions import IsAuthenticated + +from ...models.Regulator import Regulator +from ..filters.RegulatorFilter import RegulatorFilter +from ..serializers.RegulatorSerializer import RegulatorSerializer +from .mixins.UpdateModifiedMixin import UpdateModifiedMixin + + +class RegulatorViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + """ + A viewset for viewing and editing Regulator instances. + """ + + queryset = Regulator.objects.annotated().all() + authentication_classes = [SessionAuthentication, TokenAuthentication] + permission_classes = [IsAuthenticated] + serializer_class = RegulatorSerializer + filter_backends = [DjangoFilterBackend] + filterset_class = RegulatorFilter diff --git a/yeastregulatorydb/regulatory_data/api/views/__init__.py b/yeastregulatorydb/regulatory_data/api/views/__init__.py index e69de29..e03921c 100644 --- a/yeastregulatorydb/regulatory_data/api/views/__init__.py +++ b/yeastregulatorydb/regulatory_data/api/views/__init__.py @@ -0,0 +1,27 @@ +from .BindingSourceViewSet import BindingSourceViewSet +from .BindingViewSet import BindingViewSet +from .CallingCardsBackgroundViewSet import CallingCardsBackgroundViewSet +from .ChrMapViewSet import ChrMapViewSet +from .ExpressionManualQCViewSet import ExpressionManualQCViewSet +from .ExpressionSourceViewSet import ExpressionSourceViewSet +from .ExpressionViewSet import ExpressionViewSet +from .FileFormatViewSet import FileFormatViewSet +from .GenomicFeatureViewSet import GenomicFeatureViewSet +from .PromoterSetSigViewSet import PromoterSetSigViewSet +from .PromoterSetViewSet import PromoterSetViewSet +from .RegulatorViewSet import RegulatorViewSet + +__all__ = [ + "BindingSourceViewSet", + "BindingViewSet", + "CallingCardsBackgroundViewSet", + "ChrMapViewSet", + "ExpressionManualQCViewSet", + "ExpressionSourceViewSet", + "ExpressionViewSet", + "FileFormatViewSet", + "GenomicFeatureViewSet", + "PromoterSetSigViewSet", + "PromoterSetViewSet", + "RegulatorViewSet", +] diff --git a/yeastregulatorydb/regulatory_data/api/views/mixins/UpdateModifiedMixin.py b/yeastregulatorydb/regulatory_data/api/views/mixins/UpdateModifiedMixin.py new file mode 100644 index 0000000..17f6fb4 --- /dev/null +++ b/yeastregulatorydb/regulatory_data/api/views/mixins/UpdateModifiedMixin.py @@ -0,0 +1,59 @@ +""" +.. module:: UpdateModifiedMixin + :synopsis: A custom mixin for Django Rest Framework views to + automatically update the `modifiedBy` and `modified` fields when an + update operation is performed. + +This module contains the UpdateModifiedByMixin, a custom mixin for Django +Rest Framework views to automatically update the `modifiedBy` and `modified` +fields when an update operation is performed. + +Example usage: + +.. code-block:: python + + from rest_framework import viewsets + from your_app.models import YourModel + from your_app.serializers import YourModelSerializer + from UpdateModifiedMixin import UpdateModifiedMixin + + class YourModelViewSet(UpdateModifiedByMixin, viewsets.ModelViewSet): + queryset = YourModel.objects.all() + serializer_class = YourModelSerializer + +This will ensure that the `modifiedBy` field is updated with the current user +and the `modified` field is updated with the current date and time whenever +an update operation is performed on a YourModel instance. +""" +from django.utils import timezone +from rest_framework import mixins + + +class UpdateModifiedMixin(mixins.UpdateModelMixin): + """ + A custom mixin to update the `modifiedBy` and `modified` fields + automatically when an update operation is performed. + + The `modifiedBy` field will be set to the current user + (from `self.request.user`) and the `modified` field will be set to the + current date and time. + + To use this mixin, include it in your views or viewsets that require + this functionality. + + Example: + + .. code-block:: python + + class YourModelViewSet(UpdateModifiedMixin, viewsets.ModelViewSet): + queryset = YourModel.objects.all() + serializer_class = YourModelSerializer + """ + + def update(self, request, *args, **kwargs): + instance = self.get_object() # type: ignore[attr-defined] + instance.modifiedBy = self.request.user # type: ignore[attr-defined] + instance.modified = timezone.now() + instance.save() + + return super().update(request, *args, **kwargs) diff --git a/yeastregulatorydb/regulatory_data/api/views/mixins/__init__.py b/yeastregulatorydb/regulatory_data/api/views/mixins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/yeastregulatorydb/regulatory_data/migrations/0001_initial.py b/yeastregulatorydb/regulatory_data/migrations/0001_initial.py new file mode 100644 index 0000000..4f6824a --- /dev/null +++ b/yeastregulatorydb/regulatory_data/migrations/0001_initial.py @@ -0,0 +1,740 @@ +# Generated by Django 4.2.8 on 2023-12-11 03:50 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import yeastregulatorydb.regulatory_data.models.mixins.FileUploadWithIdMixin + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Binding", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ( + "batch", + models.CharField( + default="none", + help_text="A batch identifier for a set of data, eg the run number in the case of calling cards", + max_length=20, + ), + ), + ("replicate", models.PositiveIntegerField(default=1, help_text="Replicate number")), + ( + "source_orig_id", + models.CharField( + default="none", + help_text="If the data was provided by a third party, and that data has a unique identifier, it is provided here. Otherwise, the value is `none`", + max_length=20, + ), + ), + ( + "strain", + models.CharField( + default="unknown", + help_text="If the strain identifier is known, it is provided. Otherwise, the value is `unknown`", + max_length=20, + ), + ), + ( + "file", + models.FileField( + help_text="A file which stores data on regulator/DNA interaction", upload_to="temp" + ), + ), + ( + "genomic_inserts", + models.PositiveIntegerField( + default=0, + help_text="The number of inserts which map to chromosomes labelled as `genomic` in the ChrMap table", + ), + ), + ( + "mito_inserts", + models.PositiveIntegerField( + default=0, + help_text="The number of inserts which map to chromosomes labelled as mitochondrial in the ChrMap table", + ), + ), + ( + "plasmid_inserts", + models.PositiveIntegerField( + default=0, + help_text="The number of inserts which map to contigs labelled as plasmid in the ChrMap table", + ), + ), + ("notes", models.CharField(default="none", max_length=100)), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "binding", + }, + bases=( + models.Model, + yeastregulatorydb.regulatory_data.models.mixins.FileUploadWithIdMixin.FileUploadMixin, + ), + ), + migrations.CreateModel( + name="CallingCardsBackground", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("name", models.CharField(help_text="The name of the background data", max_length=10, unique=True)), + ( + "file", + models.FileField( + help_text="A file which stores data on regulator/DNA interaction", upload_to="temp" + ), + ), + ("genomic_inserts", models.PositiveIntegerField(default=0)), + ("mito_inserts", models.PositiveIntegerField(default=0)), + ("plasmid_inserts", models.PositiveIntegerField(default=0)), + ("notes", models.CharField(default="none", max_length=100)), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "callingcardsbackground", + }, + bases=( + models.Model, + yeastregulatorydb.regulatory_data.models.mixins.FileUploadWithIdMixin.FileUploadMixin, + ), + ), + migrations.CreateModel( + name="ChrMap", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("refseq", models.CharField(help_text="RefSeq chr identifiers", max_length=12, unique=True)), + ("igenomes", models.CharField(help_text="iGenomes chr identifiers", max_length=12, unique=True)), + ("ensembl", models.CharField(help_text="Ensembl chr identifiers", max_length=12, unique=True)), + ("ucsc", models.CharField(help_text="UCSC chr identifiers", max_length=12, unique=True)), + ("mitra", models.CharField(help_text="Mitra chr identifiers", max_length=15, unique=True)), + ("numbered", models.CharField(help_text="Numbered chr identifiers", max_length=12, unique=True)), + ("chr", models.CharField(help_text="Chromosome number or identifier", max_length=12, unique=True)), + ("seqlength", models.PositiveIntegerField(help_text="Sequence length of a given chromosome/contig")), + ( + "type", + models.CharField( + choices=[("genomic", "genomic"), ("mito", "mito"), ("plasmid", "plasmid")], + default="genomic", + help_text="Chromosome type, one of genomic, mito or plasmid", + max_length=8, + ), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "chrmap", + "managed": True, + }, + ), + migrations.CreateModel( + name="Expression", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ( + "batch", + models.CharField( + default="undefined", + help_text="A batch identifier for a set of data, eg the run number in the case of calling cards", + max_length=20, + ), + ), + ("replicate", models.PositiveIntegerField(default=1, help_text="Replicate number")), + ( + "control", + models.CharField( + choices=[("undefined", "undefined"), ("wt", "wt"), ("wt_mata", "wt_mata")], + default="undefined", + help_text="Intended for micro-array data, this field records the control strain used to generate the relative intensity data", + ), + ), + ( + "mechanism", + models.CharField( + choices=[("gev", "gev"), ("zev", "zev"), ("tfko", "tfko")], + default="undefined", + help_text="The mechanism by which the regulator was perturbed", + max_length=10, + ), + ), + ( + "restriction", + models.CharField( + choices=[("undefined", "undefined"), ("P", "P"), ("M", "M"), ("N", "N")], + default="undefined", + help_text="This is a feature of the McIsaac ZEV data", + max_length=10, + ), + ), + ("time", models.PositiveIntegerField(default=0, help_text="Timepoint of the experiment in minutes")), + ( + "file", + models.FileField( + help_text="A file which stores gene expression data that results from a given regulator perturbation", + upload_to="temp", + ), + ), + ( + "notes", + models.CharField(default="none", help_text="Free entry notes about the data", max_length=100), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "expression", + }, + bases=( + models.Model, + yeastregulatorydb.regulatory_data.models.mixins.FileUploadWithIdMixin.FileUploadMixin, + ), + ), + migrations.CreateModel( + name="GenomicFeature", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("start", models.PositiveIntegerField(db_index=True)), + ("end", models.PositiveIntegerField(db_index=True)), + ( + "strand", + models.CharField( + choices=[("+", "+"), ("-", "-"), ("*", "*")], db_index=True, default="*", max_length=1 + ), + ), + ("type", models.CharField(default="unknown", max_length=30)), + ("biotype", models.CharField(default="unknown", max_length=20)), + ("locus_tag", models.CharField(default="unknown", max_length=20, unique=True)), + ("symbol", models.CharField(default="unknown", max_length=20)), + ("source", models.CharField(max_length=50)), + ("alias", models.CharField(default="unknown", max_length=150)), + ("note", models.CharField(default="none", max_length=1000)), + ("chr", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.chrmap")), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "genomicfeature", + "managed": True, + }, + ), + migrations.CreateModel( + name="PromoterSet", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("name", models.CharField(max_length=10, unique=True)), + ( + "file", + models.FileField( + help_text="A bed format file where each row describes a regulatory region of a given target feature, which is identified in the 'name' column by the GenomicFeature id", + upload_to="temp", + ), + ), + ("notes", models.CharField(default="none", max_length=100)), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "promoterset", + }, + bases=( + models.Model, + yeastregulatorydb.regulatory_data.models.mixins.FileUploadWithIdMixin.FileUploadMixin, + ), + ), + migrations.CreateModel( + name="Regulator", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("under_development", models.BooleanField(default=False)), + ("notes", models.CharField(default="none", max_length=50)), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "regulator", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to="regulatory_data.genomicfeature" + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "regulator", + "managed": True, + }, + ), + migrations.CreateModel( + name="PromoterSetSig", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ( + "file", + models.FileField( + help_text="A file which stores data on regulator/DNA interaction", upload_to="temp" + ), + ), + ( + "background_id", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="regulatory_data.callingcardsbackground", + ), + ), + ( + "binding_id", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.binding"), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "promoter_id", + models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.promoterset"), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "promotersetsig", + }, + bases=( + models.Model, + yeastregulatorydb.regulatory_data.models.mixins.FileUploadWithIdMixin.FileUploadMixin, + ), + ), + migrations.CreateModel( + name="FileFormat", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("fileformat", models.CharField(max_length=20, unique=True)), + ("fields", models.CharField(max_length=200)), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "fileformat", + }, + ), + migrations.CreateModel( + name="ExpressionSource", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ( + "lab", + models.CharField( + help_text="name of the lab that generated the data. Single word preferred. No spaces -- use `_` instead", + max_length=20, + ), + ), + ( + "assay", + models.CharField( + help_text="name of the assay used to generate the data. Single word preferred. No spaces -- use `_` instead", + max_length=20, + ), + ), + ( + "workflow", + models.CharField( + default="undefined", + help_text="If known, note how the data was processed. Single word preferred. No spaces -- use `_` instead", + max_length=50, + ), + ), + ("citation", models.CharField(default="ask_admin", help_text="citation for the data", max_length=200)), + ( + "description", + models.CharField(default="none", help_text="brief description of the data", max_length=200), + ), + ( + "notes", + models.CharField(default="none", help_text="any additional notes about the data", max_length=100), + ), + ( + "fileformat_id", + models.ForeignKey( + help_text="foreign key to the fileformat table", + on_delete=django.db.models.deletion.CASCADE, + to="regulatory_data.fileformat", + ), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "expressionsource", + }, + ), + migrations.CreateModel( + name="ExpressionManualQC", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ( + "strain_verified", + models.CharField( + choices=[("yes", "yes"), ("no", "no"), ("unverified", "unverified")], + default="unverified", + max_length=10, + ), + ), + ( + "expression_id", + models.OneToOneField( + help_text="foreign key to the expression table", + on_delete=django.db.models.deletion.CASCADE, + to="regulatory_data.expression", + ), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "expressionmanualqc", + }, + ), + migrations.AddField( + model_name="expression", + name="regulator_id", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.regulator"), + ), + migrations.AddField( + model_name="expression", + name="source_id", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.expressionsource" + ), + ), + migrations.AddField( + model_name="expression", + name="uploader", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.CreateModel( + name="BindingSource", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ("lab", models.CharField(help_text="The name of the lab which generated the data", max_length=20)), + ("assay", models.CharField(help_text="name of the assay used to generate the data", max_length=20)), + ( + "workflow", + models.CharField( + default="undefined", + help_text="The workflow used to generate the data, including version", + max_length=50, + ), + ), + ( + "description", + models.CharField( + default="none", + help_text="A description of the data. include a URL to github repo with scripts describing how the data was parsed", + max_length=100, + ), + ), + ("citation", models.CharField(default="ask_admin", help_text="citation for the data", max_length=200)), + ( + "notes", + models.CharField( + default="none", + help_text="Any additional notes about the source of the binding data", + max_length=100, + ), + ), + ( + "fileformat_id", + models.ForeignKey( + help_text="Foreign key to the FileFormat table", + on_delete=django.db.models.deletion.CASCADE, + to="regulatory_data.fileformat", + ), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "bindingsource", + "unique_together": {("lab", "assay", "workflow")}, + }, + ), + migrations.CreateModel( + name="BindingManualQC", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("upload_date", models.DateField(auto_now_add=True)), + ("modified_date", models.DateTimeField(auto_now=True)), + ( + "rank_response_pass", + models.BooleanField( + default=True, + help_text="True if at least one bin in the top 100 genes with a binding signal is statistically significantly greater than random", + ), + ), + ( + "best_response_pass", + models.BooleanField( + default=True, + help_text="True if the only binding data that performs better is from the same binding source. Otherwise, False", + ), + ), + ( + "data_usable", + models.BooleanField( + default=True, + help_text="`True` if there is no reason to believe the data has technical faults. Otherwise, `False`, which recommends against using the data in analysis", + ), + ), + ( + "passing_replicate", + models.BooleanField( + default=True, + help_text="Primarily, and probably only, relevant to Calling Cards data. `True` if the replicate's hops should be counted towards the target hop count. `False` otherwise", + ), + ), + ( + "notes", + models.CharField( + default="unreviewed", + help_text="Free entry field for notes from the manual QC review", + max_length=100, + ), + ), + ( + "binding_id", + models.OneToOneField( + help_text="Foreign key to the Binding table", + on_delete=django.db.models.deletion.CASCADE, + to="regulatory_data.binding", + ), + ), + ( + "modifier", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_modifier", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "uploader", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "BindingManualQC", + }, + ), + migrations.AddField( + model_name="binding", + name="regulator", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.regulator"), + ), + migrations.AddField( + model_name="binding", + name="source_id", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="regulatory_data.bindingsource"), + ), + migrations.AddField( + model_name="binding", + name="uploader", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="%(class)s_uploader", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterUniqueTogether( + name="binding", + unique_together={("regulator", "batch", "replicate", "source_id")}, + ), + ] diff --git a/yeastregulatorydb/regulatory_data/models/BaseModel.py b/yeastregulatorydb/regulatory_data/models/BaseModel.py index 2c96d7c..0158c08 100644 --- a/yeastregulatorydb/regulatory_data/models/BaseModel.py +++ b/yeastregulatorydb/regulatory_data/models/BaseModel.py @@ -1,16 +1,3 @@ -""" -.. module:: base_model - :synopsis: Module for the BaseModel class. - -This module provides an abstract base class for models, defining common fields -for tracking the user who uploaded the data, the date of uploading, and the -last modification date and user who made the modification. It is intended to be -used as a base class for other models to inherit from. - -.. author:: Chase Mateusiak -.. date:: 2023-04-17 -.. modified:: 2023-12-07 -""" from django.conf import settings from django.db import models from django.db.models.signals import pre_save @@ -61,5 +48,5 @@ class Meta: @receiver(pre_save, sender=BaseModel) -def update_modified_date(sender, instance, **kwargs): +def update_modified_date(sender, instance, **kwargs): # pylint: disable=unused-argument instance.modified_date = timezone.now() diff --git a/yeastregulatorydb/regulatory_data/models/Binding.py b/yeastregulatorydb/regulatory_data/models/Binding.py index 9452ca3..7e285fb 100644 --- a/yeastregulatorydb/regulatory_data/models/Binding.py +++ b/yeastregulatorydb/regulatory_data/models/Binding.py @@ -1,15 +1,3 @@ -""" -.. module:: Binding - :synopsis: A model for storing filepaths to files concerning regulator/DNA binding data. - -This model is used to keep an index of the files which store binding data, -eg chip-seq, chip-exo, calling cards. More information on the data itself -is stored in the BindingSource model. - -.. author:: Chase Mateusiak -.. date:: 2023-04-17 -.. modified:: 2023-12-07 -""" import logging from django.core.files.storage import default_storage @@ -60,21 +48,18 @@ class Binding(BaseModel, FileUploadMixin): default=0, help_text="The number of inserts which map to contigs labelled as plasmid in the ChrMap table", ) - notes = models.CharField(max_length=100, default="none") + notes = models.CharField( + max_length=100, default="none", help_text="free entry text field, no more than 100 char long" + ) def __str__(self): return f"{self.source_id}_{self.regulator}__{self.batch}__{self.replicate}" class Meta: db_table = "binding" - constraints = [ - models.CheckConstraint( - check=models.Q(start__gt=0), - name="start_cannot_be_less_than_one", - ), - ] unique_together = ("regulator", "batch", "replicate", "source_id") + # pylint: disable=R0801 def save(self, *args, **kwargs): # Store the old file path old_file_name = self.file.name if self.file else None @@ -86,9 +71,11 @@ def save(self, *args, **kwargs): if old_file_name and old_file_name != new_file_name: default_storage.delete(old_file_name) + # pylint: enable=R0801 + @receiver(models.signals.post_delete, sender=Binding) -def remove_file_from_s3(sender, instance, using, **kwargs): +def remove_file_from_s3(sender, instance, using, **kwargs): # pylint: disable=unused-argument """ this is a post_delete signal. Hence, if the delete command is successful, the file will be deleted. If the delete command is successful, and for some diff --git a/yeastregulatorydb/regulatory_data/models/BindingManualQC.py b/yeastregulatorydb/regulatory_data/models/BindingManualQC.py index 10c8fe6..c1138c2 100644 --- a/yeastregulatorydb/regulatory_data/models/BindingManualQC.py +++ b/yeastregulatorydb/regulatory_data/models/BindingManualQC.py @@ -1,20 +1,6 @@ -""" -.. module:: BindingManualQC - :synopsis: A model for storing QC labels from manual review of binding data - -This model is used primarily to track the Calling Cards data developement. -However, the `rank_response_pass`, `best_response_pass` and `data_usable` -fields are relevant to all data types - -.. author:: Chase Mateusiak -.. date:: 2023-04-17 -.. modified:: 2023-12-07 -""" import logging from django.db import models -from django.db.models.signals import pre_save -from django.dispatch import receiver from .BaseModel import BaseModel @@ -26,9 +12,8 @@ class BindingManualQC(BaseModel): Store labels from QC reviews of binding data """ - binding_id = models.ForeignKey( - "Binding", on_delete=models.CASCADE, help_text="Foreign key to the Binding table", unique=True - ) + binding_id = models.OneToOneField( + "Binding", on_delete=models.CASCADE, help_text="Foreign key to the Binding table") rank_response_pass = models.BooleanField( default=True, help_text="True if at least one bin in the top 100 genes with a binding " diff --git a/yeastregulatorydb/regulatory_data/models/BindingSource.py b/yeastregulatorydb/regulatory_data/models/BindingSource.py index 7231afa..13c0c48 100644 --- a/yeastregulatorydb/regulatory_data/models/BindingSource.py +++ b/yeastregulatorydb/regulatory_data/models/BindingSource.py @@ -1,14 +1,3 @@ -""" -.. module:: BindingSource - :synopsis: A model for storing the various origins of binding data - -This model is used to store information about the source of binding data, -including how it is processed and how it was parsed into the stored format. - -.. author:: Chase Mateusiak -.. date:: 2023-04-17 -.. modified:: 2023-12-07 -""" import logging import re @@ -43,7 +32,8 @@ class BindingSource(BaseModel): description = models.CharField( max_length=100, default="none", - help_text="A description of the data. include a URL to github repo with scripts describing how the data was parsed", + help_text="A description of the data. include a URL to github " + "repo with scripts describing how the data was parsed", ) citation = models.CharField(max_length=200, default="ask_admin", help_text="citation for the data") notes = models.CharField( @@ -63,19 +53,19 @@ class Meta: @receiver(pre_save, sender=BindingSource) -def sanitize_entries(sender, instance, **kwargs): +def sanitize_entries(sender, instance, **kwargs): # pylint: disable=unused-argument """ Sanitize the lab, type and workflow fields before saving to the database """ # sanitize lab sanitized_lab = re.sub(r"[^a-zA-Z0-9_]", "_", instance.name.strip()).lower() - logger.debug(f"Original Binding Source lab: %s; Sanitized to: %s", instance.lab, sanitized_lab) + logger.debug("Original Binding Source lab: %s; Sanitized to: %s", instance.lab, sanitized_lab) instance.lab = sanitized_lab # sanitize type sanitized_type = re.sub(r"[^a-zA-Z0-9_]", "_", instance.type.strip()).lower() - logger.debug(f"Original Binding Source type: %s; Sanitized to: %s", instance.type, sanitized_type) + logger.debug("Original Binding Source type: %s; Sanitized to: %s", instance.type, sanitized_type) instance.type = sanitized_type # sanitize workflow sanitized_workflow = re.sub(r"[^a-zA-Z0-9_]", "_", instance.workflow.strip()).lower() - logger.debug(f"Original Binding Source workflow: %s; Sanitized to: %s", instance.workflow, sanitized_workflow) + logger.debug("Original Binding Source workflow: %s; Sanitized to: %s", instance.workflow, sanitized_workflow) instance.workflow = sanitized_workflow diff --git a/yeastregulatorydb/regulatory_data/models/CallingCardsBackground.py b/yeastregulatorydb/regulatory_data/models/CallingCardsBackground.py index 7628610..adf071f 100644 --- a/yeastregulatorydb/regulatory_data/models/CallingCardsBackground.py +++ b/yeastregulatorydb/regulatory_data/models/CallingCardsBackground.py @@ -1,15 +1,3 @@ -""" -.. module:: CallingCardsBackground - :synopsis: A clone of the binding model which stores the background data - used to calculate the Calling Cards enrichment score and p-values - -This model is used to keep an index of the files which store callingcards -background data. - -.. author:: Chase Mateusiak -.. date:: 2023-04-17 -.. modified:: 2023-12-07 -""" import logging from django.core.files.storage import default_storage @@ -31,37 +19,42 @@ class CallingCardsBackground(BaseModel, FileUploadMixin): max_length=10, blank=False, null=False, help_text="The name of the background data", unique=True ) file = models.FileField(upload_to="temp", help_text="A file which stores data on " "regulator/DNA interaction") - genomic_inserts = models.PositiveIntegerField(default=0) - mito_inserts = models.PositiveIntegerField(default=0) - plasmid_inserts = models.PositiveIntegerField(default=0) - notes = models.CharField(max_length=100, default="none") + genomic_inserts = models.PositiveIntegerField( + default=0, help_text="The number of inserts which map to chromosomes with type `genomic` in ChrMap" + ) + mito_inserts = models.PositiveIntegerField( + default=0, help_text="The number of inserts which map to chromosomes with type `mitochondrial` in ChrMap" + ) + plasmid_inserts = models.PositiveIntegerField( + default=0, help_text="The number of inserts which map to contigs with type `plasmid` in ChrMap" + ) + notes = models.CharField( + max_length=100, default="none", help_text="free entry text field, no more than 100 char long" + ) def __str__(self): return f"background:{self.name}" class Meta: db_table = "callingcardsbackground" - constraints = [ - models.CheckConstraint( - check=models.Q(start__gt=0), - name="start_cannot_be_less_than_one", - ), - ] + # pylint:disable=R0801 def save(self, *args, **kwargs): # Store the old file path old_file_name = self.file.name if self.file else None super().save(*args, **kwargs) - self.update_file_name("file", f"callingcards/background", "tsv.gz") + self.update_file_name("file", "callingcards/background", "tsv.gz") new_file_name = self.file.name super().save(update_fields=["file"]) # If the file name changed, delete the old file if old_file_name and old_file_name != new_file_name: default_storage.delete(old_file_name) + # pylint:enable=R0801 + @receiver(models.signals.post_delete, sender=CallingCardsBackground) -def remove_file_from_s3(sender, instance, using, **kwargs): +def remove_file_from_s3(sender, instance, using, **kwargs): # pylint: disable=unused-argument """ this is a post_delete signal. Hence, if the delete command is successful, the file will be deleted. If the delete command is successful, and for some diff --git a/yeastregulatorydb/regulatory_data/models/ChrMap.py b/yeastregulatorydb/regulatory_data/models/ChrMap.py index b46c69f..24a7b8c 100644 --- a/yeastregulatorydb/regulatory_data/models/ChrMap.py +++ b/yeastregulatorydb/regulatory_data/models/ChrMap.py @@ -1,18 +1,3 @@ -""" -.. module:: chrmap - :synopsis: Module for the `ChrMap` model that stores chromosome mapping - information for different genome assemblies. - -This module defines the `ChrMap` model used to store the chromosome mapping -information for different genome sources, eg between igenomes, ucsc and -refseq. This table classifies chromosomes as genomic or mitochondrial, -and adds and identifies as such some plasmid sequences which are added to -callingcards strains. It additionally has the seqlength of each contig. - -.. moduleauthor:: Chase Mateusiak -.. date:: 2023-04-21 -.. modified::2023-12-07 -""" from django.db import models from .BaseModel import BaseModel @@ -67,7 +52,7 @@ class ChrMap(BaseModel): ) def __str__(self): - return f"{self.ucsc}(chrID:{self.pk})" # pylint: disable=no-member + return f"{self.ucsc}(chrID:{self.pk})" class Meta: managed = True diff --git a/yeastregulatorydb/regulatory_data/models/Expression.py b/yeastregulatorydb/regulatory_data/models/Expression.py index f027523..f6cea33 100644 --- a/yeastregulatorydb/regulatory_data/models/Expression.py +++ b/yeastregulatorydb/regulatory_data/models/Expression.py @@ -1,17 +1,3 @@ -""" -.. module:: expression - :synopsis: A model for storing filepaths to files concerning gene - expression after perturbing a presumed genetic regulator. - -This model is used to keep an index of the files which store expression data, -eg mcisaac zev/gev overexpression, kemmeren transcription factor knockout -(TFKO) and hu TFKO. More information on the data itself is stored in the -ExpressionSource model. - -.. moduleauthor:: Chase Mateusiak -.. date:: 2023-04-21 -.. modified::2023-12-07 -""" import logging from django.core.files.storage import default_storage @@ -39,7 +25,8 @@ class Expression(BaseModel, FileUploadMixin): control = models.CharField( choices=[("undefined", "undefined"), ("wt", "wt"), ("wt_mata", "wt_mata")], default="undefined", - help_text="Intended for micro-array data, this field records the control strain used to generate the relative intensity data", + help_text="Intended for micro-array data, this field records the " + "control strain used to generate the relative intensity data", ) mechanism = models.CharField( choices=[("gev", "gev"), ("zev", "zev"), ("tfko", "tfko")], @@ -66,13 +53,8 @@ def __str__(self): class Meta: db_table = "expression" - constraints = [ - models.CheckConstraint( - check=models.Q(start__gt=0), - name="start_cannot_be_less_than_one", - ), - ] + # pylint: disable=R0801 def save(self, *args, **kwargs): # Store the old file path old_file_name = self.file.name if self.file else None @@ -84,9 +66,12 @@ def save(self, *args, **kwargs): if old_file_name and old_file_name != new_file_name: default_storage.delete(old_file_name) + # pylint: enable=R0801 + +# pylint: disable=R0801 @receiver(models.signals.post_delete, sender=Expression) -def remove_file_from_s3(sender, instance, using, **kwargs): +def remove_file_from_s3(sender, instance, using, **kwargs): # pylint: disable=unused-argument """ this is a post_delete signal. Hence, if the delete command is successful, the file will be deleted. If the delete command is successful, and for some diff --git a/yeastregulatorydb/regulatory_data/models/ExpressionManualQC.py b/yeastregulatorydb/regulatory_data/models/ExpressionManualQC.py index fe609ed..ce7358c 100644 --- a/yeastregulatorydb/regulatory_data/models/ExpressionManualQC.py +++ b/yeastregulatorydb/regulatory_data/models/ExpressionManualQC.py @@ -1,21 +1,6 @@ -""" -.. module:: ExpressionManualQC - :synopsis: A model for storing labels and notes from manual QC reviews of - the data - - This model is used to keep track of decisions and labels relating to the - Expression data QC. Since we have very little access to the raw data, - this (will eventually) focus on the comparison between the expression set - and the binding data. - -.. moduleauthor:: Chase Mateusiak -.. date:: 2023-04-21 -.. modified::2023-12-07 -""" import logging from django.db import models -from django.dispatch import receiver from .BaseModel import BaseModel @@ -27,13 +12,16 @@ class ExpressionManualQC(BaseModel): Record labels from QC reviews of expression data """ - expression_id = models.ForeignKey( - "Expression", on_delete=models.CASCADE, help_text="foreign key to the expression table" + expression_id = models.OneToOneField( + "Expression", + on_delete=models.CASCADE, + help_text="foreign key to the expression table", ) strain_verified = models.CharField( max_length=10, choices=(("yes", "yes"), ("no", "no"), ("unverified", "unverified")), default="unverified", + help_text="whether the strain is verified", ) def __str__(self): diff --git a/yeastregulatorydb/regulatory_data/models/ExpressionSource.py b/yeastregulatorydb/regulatory_data/models/ExpressionSource.py index 1c2bae1..a2fd9bb 100644 --- a/yeastregulatorydb/regulatory_data/models/ExpressionSource.py +++ b/yeastregulatorydb/regulatory_data/models/ExpressionSource.py @@ -1,14 +1,3 @@ -""" -.. module:: ExpressionSource - :synopsis: A model for storing the various origins of expression data - -This model is used to store information about the source of expression data, -including how it is processed and how it was parsed into the stored format. - -.. author:: Chase Mateusiak -.. date:: 2023-04-17 -.. modified:: 2023-12-07 -""" import logging import re @@ -58,16 +47,16 @@ class Meta: @receiver(pre_save, sender=ExpressionSource) -def sanitize_entries(sender, instance, **kwargs): +def sanitize_entries(sender, instance, **kwargs): # pylint: disable=unused-argument # sanitize lab sanitized_lab = re.sub(r"[^a-zA-Z0-9_]", "_", instance.name.strip()).lower() - logger.debug(f"Original Expression Source lab: %s; Sanitized to: %s", instance.lab, sanitized_lab) + logger.debug("Original Expression Source lab: %s; Sanitized to: %s", instance.lab, sanitized_lab) instance.lab = sanitized_lab # sanitize type sanitized_type = re.sub(r"[^a-zA-Z0-9_]", "_", instance.type.strip()).lower() - logger.debug(f"Original Expression Source type: %s; Sanitized to: %s", instance.type, sanitized_type) + logger.debug("Original Expression Source type: %s; Sanitized to: %s", instance.type, sanitized_type) instance.type = sanitized_type # sanitize workflow sanitized_workflow = re.sub(r"[^a-zA-Z0-9_]", "_", instance.workflow.strip()).lower() - logger.debug(f"Original Expression Source workflow: %s; Sanitized to: %s", instance.workflow, sanitized_workflow) + logger.debug("Original Expression Source workflow: %s; Sanitized to: %s", instance.workflow, sanitized_workflow) instance.workflow = sanitized_workflow diff --git a/yeastregulatorydb/regulatory_data/models/FileFormat.py b/yeastregulatorydb/regulatory_data/models/FileFormat.py index 100d803..ec6e306 100644 --- a/yeastregulatorydb/regulatory_data/models/FileFormat.py +++ b/yeastregulatorydb/regulatory_data/models/FileFormat.py @@ -14,8 +14,19 @@ class FileFormat(BaseModel): Store the filetype name and a comma separated string of field names """ - fileformat = models.CharField(max_length=20, blank=False, null=False, unique=True) - fields = models.CharField(max_length=200, blank=False, null=False) + fileformat = models.CharField( + max_length=20, + blank=False, + null=False, + unique=True, + help_text="A filetype name. This should be short, eg 'bed' or 'qbed' or 'tsv'", + ) + fields = models.CharField( + max_length=200, + blank=False, + null=False, + help_text='A comma separated list of field names. Eg "chromosome, start, end, strand, name, score"', + ) def __str__(self): return f"{self.fileformat}" @@ -25,5 +36,5 @@ class Meta: @receiver(pre_save, sender=FileFormat) -def sanitize_entries(sender, instance, **kwargs): +def sanitize_entries(sender, instance, **kwargs): # pylint: disable=unused-argument instance.fields = instance.fields.replace(" ", "").strip() # remove spaces diff --git a/yeastregulatorydb/regulatory_data/models/GenomicFeature.py b/yeastregulatorydb/regulatory_data/models/GenomicFeature.py index d53d1b4..e2bf275 100644 --- a/yeastregulatorydb/regulatory_data/models/GenomicFeature.py +++ b/yeastregulatorydb/regulatory_data/models/GenomicFeature.py @@ -1,22 +1,10 @@ -""" -.. module:: GenomicFeature - :synopsis: Module for the `GenomicFeature` model that stores genomic coordinates and - annotations for genomic features. - -This module defines the `GenomicFeature` model used to store genomic coordinates and -annotations for genomic features, including the `chr`, `start`, `end`, `strand`, `type`, -`biotype`, `locus_tag`, `symbol`, `source`, `alias`, and `note` fields. - -.. moduleauthor:: Chase Mateusiak -.. date:: 2023-04-21 -""" from django.db import models from .BaseModel import BaseModel from .mixins.GenomicCoordinatesMixin import GenonomicCoordinatesMixin -class GenomicFeatureManager(models.Manager): +class GenomicFeatureManager(models.Manager): # pylint: disable=too-few-public-methods """Custom manager for the GenomicFeature model Example usage: @@ -69,24 +57,56 @@ class GenomicFeature(GenonomicCoordinatesMixin, BaseModel): # get all GenomicFeature records all_features = GenomicFeature.objects.all() - """ objects = GenomicFeatureManager() - type = models.CharField(max_length=30, default="unknown") - biotype = models.CharField(max_length=20, default="unknown") + chr = models.ForeignKey( + "ChrMap", + on_delete=models.CASCADE, + help_text="ForeignKey to the `ChrMap` model, representing the chromosome that the genomic feature is located on", + ) + type = models.CharField( + max_length=30, + default="unknown", + help_text="CharField with a max length of 30, representing the type of the genomic feature", + ) + biotype = models.CharField( + max_length=20, + default="unknown", + help_text="CharField with a max length of 20, representing the biotype of the feature", + ) # note: in the save method below, a unique integer is appended to the # default value if the this field is left blank on input - locus_tag = models.CharField(unique=True, max_length=20, default="unknown") + locus_tag = models.CharField( + unique=True, + max_length=20, + default="unknown", + help_text="CharField with a max length of 20 and a unique constraint, representing the locus tag of the feature, eg YAL001C", + ) # note: in the save method below, a unique integer is appended to the # default value if the this field is left blank on input - symbol = models.CharField(max_length=20, default="unknown") - source = models.CharField(max_length=50) + symbol = models.CharField( + max_length=20, + default="unknown", + help_text="CharField with a max length of 20, representing the feature symbol (eg GAL4)", + ) + source = models.CharField( + max_length=50, + help_text="CharField with a max length of 50, representing the source of the feature information", + ) # note: in the save method below, a unique integer is appended to the # default value if the this field is left blank on input - alias = models.CharField(max_length=150, default="unknown") - note = models.CharField(max_length=1000, default="none") + alias = models.CharField( + max_length=150, + default="unknown", + help_text="CharField with a max length of 150, representing the alias of the feature", + ) + note = models.CharField( + max_length=1000, + default="none", + help_text="CharField with a max length of 1000, representing any notes about the feature", + ) def save(self, *args, **kwargs): """ @@ -112,7 +132,7 @@ def __str__(self): """ Returns a string representation of the `GenomicFeature` model. """ - return f"{self.symbol}({self.locus_tag}; pk: {self.pk}" # pylint: disable=no-member # noqa + return f"{self.symbol}({self.locus_tag}; pk: {self.pk}" class Meta: managed = True diff --git a/yeastregulatorydb/regulatory_data/models/PromoterSet.py b/yeastregulatorydb/regulatory_data/models/PromoterSet.py index e5aaf77..1ced5ae 100644 --- a/yeastregulatorydb/regulatory_data/models/PromoterSet.py +++ b/yeastregulatorydb/regulatory_data/models/PromoterSet.py @@ -17,7 +17,7 @@ class PromoterSet(BaseModel, FileUploadMixin): join with the identifier columns of the expression and binding data """ - name = models.CharField(max_length=10, unique=True) + name = models.CharField(max_length=10, unique=True, help_text="The name of the promoter set, eg `orf` or `yiming`") file = models.FileField( upload_to="temp", help_text="A bed format file where each row " @@ -25,7 +25,9 @@ class PromoterSet(BaseModel, FileUploadMixin): "target feature, which is identified in the " "'name' column by the GenomicFeature id", ) - notes = models.CharField(max_length=100, default="none") + notes = models.CharField( + max_length=100, default="none", help_text="free entry text field, no more than 100 char long" + ) def __str__(self): return f"{self.name}" @@ -33,6 +35,7 @@ def __str__(self): class Meta: db_table = "promoterset" + # pylint:disable=R0801 def save(self, *args, **kwargs): # Store the old file path old_file_name = self.file.name if self.file else None @@ -44,9 +47,11 @@ def save(self, *args, **kwargs): if old_file_name and old_file_name != new_file_name: default_storage.delete(old_file_name) + # pylint:enable=R0801 + @receiver(models.signals.post_delete, sender=PromoterSet) -def remove_file_from_s3(sender, instance, using, **kwargs): +def remove_file_from_s3(sender, instance, using, **kwargs): # pylint: disable=unused-argument """ this is a post_delete signal. Hence, if the delete command is successful, the file will be deleted. If the delete command is successful, and for some diff --git a/yeastregulatorydb/regulatory_data/models/PromoterSetSig.py b/yeastregulatorydb/regulatory_data/models/PromoterSetSig.py index 533ffad..232bacc 100644 --- a/yeastregulatorydb/regulatory_data/models/PromoterSetSig.py +++ b/yeastregulatorydb/regulatory_data/models/PromoterSetSig.py @@ -15,41 +15,47 @@ class PromoterSetSig(BaseModel, FileUploadMixin): Store PromoterSetSig data """ - binding_id = models.ForeignKey("Binding", on_delete=models.CASCADE) - promoter_id = models.ForeignKey("Promoter", on_delete=models.CASCADE) - background_id = models.ForeignKey("CallingCardsBackground", on_delete=models.CASCADE, default="undefined") + binding_id = models.ForeignKey("Binding", on_delete=models.CASCADE, help_text="foreign key to the 'Binding' table") + promoter_id = models.ForeignKey( + "PromoterSet", on_delete=models.CASCADE, help_text="foreign key to the 'promoter' table" + ) + # note: in the serializer, when a user makes a GET request, a null value + # is transformed to the string 'undefined' prior to returning to client + background_id = models.ForeignKey( + "CallingCardsBackground", + on_delete=models.CASCADE, + blank=True, + null=True, + help_text="foreign key to the 'CallingCardsBackground' table", + ) file = models.FileField(upload_to="temp", help_text="A file which stores data on " "regulator/DNA interaction") - genomic_inserts = models.PositiveIntegerField(default=0) - mito_inserts = models.PositiveIntegerField(default=0) - plasmid_inserts = models.PositiveIntegerField(default=0) - notes = models.CharField(max_length=100, default="none") def __str__(self): - return f"pk:{self.pk};binding_id:{self.binding_id};promoter_id:{self.promoter_id};background_id:{self.background_id}" + return ( + f"pk:{self.pk};binding_id:{self.binding_id};" + f"promoter_id:{self.promoter_id};background_id:{self.background_id}" + ) class Meta: db_table = "promotersetsig" - constraints = [ - models.CheckConstraint( - check=models.Q(start__gt=0), - name="start_cannot_be_less_than_one", - ), - ] + # pylint:disable=R0801 def save(self, *args, **kwargs): # Store the old file path old_file_name = self.file.name if self.file else None super().save(*args, **kwargs) - self.update_file_name("file", f"promotersetsig", "tsv.gz") + self.update_file_name("file", "promotersetsig", "tsv.gz") new_file_name = self.file.name super().save(update_fields=["file"]) # If the file name changed, delete the old file if old_file_name and old_file_name != new_file_name: default_storage.delete(old_file_name) + # pylint:enable=R0801 + @receiver(models.signals.post_delete, sender=PromoterSetSig) -def remove_file_from_s3(sender, instance, using, **kwargs): +def remove_file_from_s3(sender, instance, using, **kwargs): # pylint: disable=unused-argument """ this is a post_delete signal. Hence, if the delete command is successful, the file will be deleted. If the delete command is successful, and for some diff --git a/yeastregulatorydb/regulatory_data/models/Regulator.py b/yeastregulatorydb/regulatory_data/models/Regulator.py index 4fc1977..dd23881 100644 --- a/yeastregulatorydb/regulatory_data/models/Regulator.py +++ b/yeastregulatorydb/regulatory_data/models/Regulator.py @@ -1,14 +1,3 @@ -""" -.. module:: Regulator - :synopsis: Model for storing a list of transcription factors interrogated - with calling cards. - -.. moduleauthor:: Chase Mateusiak -.. date:: 2023-11-20 - -This module defines the `Regulator` model, which is used to store a list of the -transcription factors interrogated with calling cards. -""" from django.db import models from .BaseModel import BaseModel @@ -20,13 +9,14 @@ class RegulatorManager(models.Manager): Example usage: .. code-block:: python - # in RegulatorManager - def custom_method(self): - # do some operations eg filter, exclude, annotation... - # and return - # get all Regulator records that are still under development - my_regulator_set = Regulator.objects.custom_method() + # in RegulatorManager + def custom_method(self): + # do some operations eg filter, exclude, annotation... + # and return + + # get all Regulator records that are still under development + my_regulator_set = Regulator.objects.custom_method() """ def under_development(self): @@ -39,7 +29,7 @@ def annotated(self): ) .annotate( regulator_locus_tag=models.F("regulator__locus_tag"), - regulator_gene=models.F("regulator__gene"), + regulator_gene=models.F("regulator__symbol"), ) .values("regulator_id", "regulator_locus_tag", "regulator_gene") ) @@ -72,9 +62,15 @@ class Regulator(BaseModel): objects = RegulatorManager() - regulator = models.ForeignKey("GenomicFeature", models.PROTECT, db_index=True) - under_development = models.BooleanField(default=False) - notes = models.CharField(max_length=50, default="none") + regulator = models.ForeignKey( + "GenomicFeature", models.PROTECT, db_index=True, help_text="foreign key to the `id` field of GenomicFeature" + ) + under_development = models.BooleanField( + default=False, help_text="whether the regulator is being currently interrogated in any experiments" + ) + notes = models.CharField( + max_length=50, default="none", help_text="free entry text field, no more than 50 char long" + ) def __str__(self): return str(self.regulator) + "_" + str(self.pk) diff --git a/yeastregulatorydb/regulatory_data/models/__init__.py b/yeastregulatorydb/regulatory_data/models/__init__.py index e69de29..a4be954 100644 --- a/yeastregulatorydb/regulatory_data/models/__init__.py +++ b/yeastregulatorydb/regulatory_data/models/__init__.py @@ -0,0 +1,29 @@ +from .Binding import Binding +from .BindingManualQC import BindingManualQC +from .BindingSource import BindingSource +from .CallingCardsBackground import CallingCardsBackground +from .ChrMap import ChrMap +from .Expression import Expression +from .ExpressionManualQC import ExpressionManualQC +from .ExpressionSource import ExpressionSource +from .FileFormat import FileFormat +from .GenomicFeature import GenomicFeature +from .PromoterSet import PromoterSet +from .PromoterSetSig import PromoterSetSig +from .Regulator import Regulator + +__all__ = [ + "Binding", + "BindingManualQC", + "BindingSource", + "CallingCardsBackground", + "ChrMap", + "Expression", + "ExpressionManualQC", + "ExpressionSource", + "FileFormat", + "GenomicFeature", + "PromoterSet", + "PromoterSetSig", + "Regulator", +] diff --git a/yeastregulatorydb/regulatory_data/models/mixins/FileUploadWithIdMixin.py b/yeastregulatorydb/regulatory_data/models/mixins/FileUploadWithIdMixin.py index 39b333f..c9e4348 100644 --- a/yeastregulatorydb/regulatory_data/models/mixins/FileUploadWithIdMixin.py +++ b/yeastregulatorydb/regulatory_data/models/mixins/FileUploadWithIdMixin.py @@ -7,11 +7,7 @@ logger = logging.getLogger(__name__) -class FileUploadMetaclass(ModelBase, type): - pass - - -class HasPkProtocol(Protocol): +class HasPkProtocol(Protocol): # pylint: disable=too-few-public-methods """ from copilot: In Python's typing system, a `Protocol` is a way to define an interface that a class must adhere to. It's a way to say "any class that @@ -32,7 +28,7 @@ class HasPkProtocol(Protocol): pk: Optional[int] -class FileUploadMixin(metaclass=FileUploadMetaclass): +class FileUploadMixin(): # pylint: disable=too-few-public-methods """ A mixin for models that have a file field that should be uploaded to a specific directory and renamed based on the instance's ID. This mixin @@ -75,7 +71,7 @@ def update_file_name(self, file_field_name: str, upload_dir: str, extension: str # raise AttributeError if self does not have a pk attribute if not self_with_pk.pk: raise AttributeError(f"{self} does not have a pk attribute") - logger.debug("Updating file name for %s to " "%s/%s.%s", self_with_pk, upload_dir, self_with_pk.pk, extension) + logger.debug("Updating file name for %s to %s/%s.%s", self_with_pk, upload_dir, self_with_pk.pk, extension) file_field = getattr(self, file_field_name, None) if file_field and self_with_pk.pk and file_field.name: # Define new filename with ID diff --git a/yeastregulatorydb/regulatory_data/models/mixins/GenomicCoordinatesMixin.py b/yeastregulatorydb/regulatory_data/models/mixins/GenomicCoordinatesMixin.py index cfab831..396e1e3 100644 --- a/yeastregulatorydb/regulatory_data/models/mixins/GenomicCoordinatesMixin.py +++ b/yeastregulatorydb/regulatory_data/models/mixins/GenomicCoordinatesMixin.py @@ -1,16 +1,23 @@ -"""Mixins which may be useful for storing genomic data""" -from django.db import models +"""Mixins which may be useful for storing genomic data + +.. author:: Chase Mateusiak +.. date:: 2023-04-20 +.. modified:: 2023-12-07 +""" from enum import Enum +from django.db import models + class Strand(Enum): """ - Enum representing the strand of a genomic feature. This is used to + Enum representing the strand of a genomic feature. This is used to provide some flexibility in how these values are stored in the database. """ - POSITIVE = '+' - NEGATIVE = '-' - UNSTRANDED = '*' + + POSITIVE = "+" + NEGATIVE = "-" + UNSTRANDED = "*" class GenonomicCoordinatesMixin(models.Model): @@ -32,38 +39,30 @@ class GenonomicCoordinatesMixin(models.Model): - start_cannot_be_less_than_one: start position must be greater than 0. - end_cannot_exceed_chromosome_length: end position must be less than or equal to the length of the chromosome. - - .. author:: Chase Mateusiak - .. date:: 2023-04-20 """ - STRAND_CHOICES = ((Strand.POSITIVE.value, '+'), - (Strand.NEGATIVE.value, '-'), - (Strand.UNSTRANDED.value, '*')) - chr = models.ForeignKey( - 'ChrMap', models.PROTECT, - db_index=True) - start = models.PositiveIntegerField( - db_index=True - ) - end = models.PositiveIntegerField( - db_index=True - ) + STRAND_CHOICES = ((Strand.POSITIVE.value, "+"), (Strand.NEGATIVE.value, "-"), (Strand.UNSTRANDED.value, "*")) + + chr = models.ForeignKey("ChrMap", models.PROTECT, db_index=True, help_text="foreign key to the `id` field of ChrMap") # type: ignore[misc] + start = models.PositiveIntegerField(db_index=True, help_text="start position of the feature") + end = models.PositiveIntegerField(db_index=True, help_text="end position of the feature") strand = models.CharField( max_length=1, choices=STRAND_CHOICES, default=Strand.UNSTRANDED.value, - db_index=True) + db_index=True, + help_text="strand of the feature, one of +, -, or *", + ) - class Meta: # pylint: disable=C0115 + class Meta: abstract = True constraints = [ models.CheckConstraint( check=models.Q(start__gt=0), - name='start_cannot_be_less_than_one', + name="start_cannot_be_less_than_one", ), models.CheckConstraint( - check=models.Q(end__lte=models.F('chr__seqlength')), - name='end_cannot_exceed_chromosome_length', - ) + check=models.Q(end__lte=models.F("chr__seqlength")), + name="end_cannot_exceed_chromosome_length", + ), ] diff --git a/yeastregulatorydb/users/adapters.py b/yeastregulatorydb/users/adapters.py index c7ac362..e7bd92e 100644 --- a/yeastregulatorydb/users/adapters.py +++ b/yeastregulatorydb/users/adapters.py @@ -9,6 +9,7 @@ if typing.TYPE_CHECKING: from allauth.socialaccount.models import SocialLogin + from yeastregulatorydb.users.models import User diff --git a/yeastregulatorydb/users/admin.py b/yeastregulatorydb/users/admin.py index d81446f..b42fb79 100644 --- a/yeastregulatorydb/users/admin.py +++ b/yeastregulatorydb/users/admin.py @@ -1,7 +1,7 @@ from django.conf import settings from django.contrib import admin from django.contrib.auth import admin as auth_admin -from django.contrib.auth import get_user_model, decorators +from django.contrib.auth import decorators, get_user_model from django.utils.translation import gettext_lazy as _ from yeastregulatorydb.users.forms import UserAdminChangeForm, UserAdminCreationForm diff --git a/yeastregulatorydb/users/api/serializers.py b/yeastregulatorydb/users/api/serializers.py index d77f7e9..926915a 100644 --- a/yeastregulatorydb/users/api/serializers.py +++ b/yeastregulatorydb/users/api/serializers.py @@ -3,7 +3,6 @@ from yeastregulatorydb.users.models import User as UserType - User = get_user_model() diff --git a/yeastregulatorydb/users/apps.py b/yeastregulatorydb/users/apps.py index c769218..dbf90b1 100644 --- a/yeastregulatorydb/users/apps.py +++ b/yeastregulatorydb/users/apps.py @@ -8,6 +8,6 @@ class UsersConfig(AppConfig): def ready(self): try: - import yeastregulatorydb.users.signals # noqa: F401 + import yeastregulatorydb.users.signals # noqa: F401 #type: ignore except ImportError: pass diff --git a/yeastregulatorydb/users/migrations/0001_initial.py b/yeastregulatorydb/users/migrations/0001_initial.py index 5a902a6..f48aca9 100644 --- a/yeastregulatorydb/users/migrations/0001_initial.py +++ b/yeastregulatorydb/users/migrations/0001_initial.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -30,9 +29,7 @@ class Migration(migrations.Migration): ("password", models.CharField(max_length=128, verbose_name="password")), ( "last_login", - models.DateTimeField( - blank=True, null=True, verbose_name="last login" - ), + models.DateTimeField(blank=True, null=True, verbose_name="last login"), ), ( "is_superuser", @@ -41,26 +38,21 @@ class Migration(migrations.Migration): 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." - }, + 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() - ], + validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name="username", ), ), ( "email", - models.EmailField( - blank=True, max_length=254, verbose_name="email address" - ), + models.EmailField(blank=True, max_length=254, verbose_name="email address"), ), ( "is_staff", @@ -80,15 +72,11 @@ class Migration(migrations.Migration): ), ( "date_joined", - models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" - ), + models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"), ), ( "name", - models.CharField( - blank=True, max_length=255, verbose_name="Name of User" - ), + models.CharField(blank=True, max_length=255, verbose_name="Name of User"), ), ( "groups", diff --git a/yeastregulatorydb/users/tests/test_views.py b/yeastregulatorydb/users/tests/test_views.py index 3fcd049..2b6e9c0 100644 --- a/yeastregulatorydb/users/tests/test_views.py +++ b/yeastregulatorydb/users/tests/test_views.py @@ -12,11 +12,7 @@ from yeastregulatorydb.users.forms import UserAdminChangeForm from yeastregulatorydb.users.models import User from yeastregulatorydb.users.tests.factories import UserFactory -from yeastregulatorydb.users.views import ( - UserRedirectView, - UserUpdateView, - user_detail_view, -) +from yeastregulatorydb.users.views import UserRedirectView, UserUpdateView, user_detail_view pytestmark = pytest.mark.django_db diff --git a/yeastregulatorydb/users/urls.py b/yeastregulatorydb/users/urls.py index 359e0d6..86b6512 100644 --- a/yeastregulatorydb/users/urls.py +++ b/yeastregulatorydb/users/urls.py @@ -1,10 +1,6 @@ from django.urls import path -from yeastregulatorydb.users.views import ( - user_detail_view, - user_redirect_view, - user_update_view, -) +from yeastregulatorydb.users.views import user_detail_view, user_redirect_view, user_update_view app_name = "users" urlpatterns = [