django-jsonform icon indicating copy to clipboard operation
django-jsonform copied to clipboard

make it simpler to access schema through foreign key relation

Open johanneswilm opened this issue 3 years ago • 4 comments

Hey, thanks for this library. I have a use-case that I believe is fairly normal as I've had to do the same thing in multiple different situations/projects. Using your library I've had to do some workarounds to get there.

The setup is this: I have to store annual surveys in the database. The surveys are always have a few fields that always need to be included (name, date, etc.) and other fields that change every year. So I create two models in the database:

class SurveyType(models.Model):
    name = models.CharField(max_length=255)
    schema = models.JSONField(default=dict)

    def __str__(self):
        return self.name

class SurveyEntry(models.Model):
    participant_name = models.Charfield(max_length=255)
    date = models.DateField()
    survey_type = models.ForeignKey(
        SurveyType,
        on_delete=models.CASCADE,
    )
    survey_data = jsonformJSONField(schema=lambda instance: instance.survey_type.schema)
    

This looks good, but it won't work as initially when entering a new SurveyEntry, no survey_type has been defined. It would be good if there was a way to handle that. The work around I found was to do this to SurveyEntry:

class SurveyEntry(models.Model):
    participant_name = models.Charfield(max_length=255)
    date = models.DateField()
    survey_type = models.ForeignKey(
        SurveyType,
        on_delete=models.CASCADE,
    )
    survey_data = jsonformJSONField(
        schema=lambda instance=None: instance.survey_type.schema
        if instance and hasattr(instance, "survey_type")
        else {"type": "object", "properties": {}},
        null=True,
        blank=True,
    )
    def save(self, *args, **kwargs):
        if not bool(self.survey_data):
            # This was likely based on the default schema. Remove again.
            self.survey_data = None
        return super().save(*args, **kwargs)

johanneswilm avatar Aug 22 '22 07:08 johanneswilm

I wouldn't call it a workaround. Since you've got a callable schema, it's your responsibility to handle the errors within that callable.

As the callable is your written code, I don't think the field should automatically handle the exceptions raised in it.

bhch avatar Aug 22 '22 08:08 bhch

ok, how about adding something to the documentation about it then? Then others can benefit from me having researched this once already.

johanneswilm avatar Aug 22 '22 08:08 johanneswilm

I'll update the docs. Thank you for your suggestions.

bhch avatar Aug 22 '22 09:08 bhch

I have to use form to make it works


class FlowerPackForm(forms.ModelForm):
    class Meta:
        model = FlowerPack
        exclude = ["is_removed"]

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        instance = kwargs.get("instance", None)
        if instance and hasattr(instance, "sku"):
            self.fields["attributes"].widget.schema = instance.sku.attribute_schema

I have try to modify the model meta field but it seams have cache, so it not allways work.

class FlowerPack(BaseModel):
    sku = models.ForeignKey(Sku, models.CASCADE, related_name="packs")
    attributes = JSONField(
        schema={"type": "object", "properties": {}},
        null=True,
        blank=True,
        verbose_name="SKU 属性列表",
    )
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        if hasattr(self, "sku"):
            field = self._meta.get_field("attributes")
            field.schema = self.sku.attribute_schema

yswtrue avatar Sep 30 '23 20:09 yswtrue