Git Product home page Git Product logo

django-approval's People

Contributors

artscoop avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

stavapl

django-approval's Issues

Wrong package on pypi

Seems like when you install django-approval using pip install django-approval, the main package approval is not added to site-packages. However, I think the master branch is correct. Maybe a new push on pypi?

Object of type ManyRelatedManager not JSON Serializable.

Hi! First, thanks for making this library!

I'm having an issue with it and m2m models using postgres and psycopg.

Here are is a relevant set of models, one of which inherits the MonitoredModel and the other of which is the Approval that is in the Sandbox.

from django.db import models
import uuid
from suppliers.models import Supplier
from django.urls import reverse
from approval.models import MonitoredModel, Sandbox, SandboxMeta
from django.conf import settings

class Bid(MonitoredModel):
    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.DO_NOTHING,
        related_name="bids",
        null=True,
        blank=True,
    )
    is_visible = models.BooleanField(
        default=True,
        verbose_name="visible"
    )
    code = models.CharField(
        max_length=24,
        null=False,
        blank=False,
        default="BID Code",
        unique=True,
    )
    name = models.CharField(
        max_length=256,
        null=True,
        blank=False,
        default="BID Name",
    )
    suppliers_invited = models.ManyToManyField(
        Supplier,
        related_name="suppliers_invited",
        blank=True,
    )
    suppliers_responded = models.ManyToManyField(
        Supplier,
        related_name="suppliers_responded",
        blank=True,
        verbose_name="Suppliers who responded to the tender",
    )
    suppliers_technically_responsive = models.ManyToManyField(
        Supplier,
        related_name="suppliers_technically_responsive",
        blank=True,
    )
    suppliers_financially_responsive = models.ManyToManyField(
        Supplier,
        related_name="suppliers_financially_responsive",
        blank=True,
    )

    class Meta:
        ordering = ['code']
    def __str__(self):
        return self.code

    def get_absolute_url(self):
        return reverse('bid_details', kwargs={'pk': self.id})

class BidApproval(Sandbox, metaclass=SandboxMeta):
    base = Bid
    approval_fields = ['code', 'name',
                       'suppliers_invited',
                       'suppliers_responded',
                       'suppliers_technically_responsive',
                       'suppliers_financially_responsive',
                       ]
    approval_default = {'is_visible': False, }
    auto_approve_staff = False
    auto_approve_new = False
    auto_approve_by_request = False
    delete_on_approval = False

    def _get_authors(self):
        return [self.source.user]


Here is the output from the interactive shell when I create and try to save a Bid that inherits the MonitoredModel class. First, the relevant content:

>>> from bids.models import *
>>> test = Bid(name="test", code="test")
>>> all_bids = Bid.objects.all()
>>> print(all_bids)
<QuerySet []>
>>> test.save()
Traceback (most recent call last):
...
  File "/usr/local/lib/python3.12/site-packages/approval/listeners/monitored.py", line 37, in before_save
    instance.approval._update_sandbox()
  File "/usr/local/lib/python3.12/site-packages/approval/models/monitoring.py", line 215, in _update_sandbox
    self.save()
  File "/usr/local/lib/python3.12/site-packages/approval/models/monitoring.py", line 117, in save
    return super().save(**options)
           ^^^^^^^^^^^^^^^^^^^^^^^
...

Now, the full error message:

>>> from bids.models import *
>>> test = Bid(name="test", code="test")
>>> all_bids = Bid.objects.all()
>>> print(all_bids)
<QuerySet []>
>>> test.save()
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 114, in debug_sql
    yield
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 102, in execute
    return super().execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 67, in execute
    return self._execute_with_wrappers(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/cursor.py", line 728, in execute
    self._conn.wait(
  File "/usr/local/lib/python3.12/site-packages/psycopg/connection.py", line 969, in wait
    return waiting.wait(gen, self.pgconn.socket, timeout=timeout)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "psycopg_binary/_psycopg/waiting.pyx", line 190, in psycopg_binary._psycopg.wait_c
  File "/usr/local/lib/python3.12/site-packages/psycopg/cursor.py", line 210, in _execute_gen
    pgq = self._convert_query(query, params)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/client_cursor.py", line 79, in _convert_query
    pgq.convert(query, params)
  File "/usr/local/lib/python3.12/site-packages/psycopg/_queries.py", line 213, in convert
    self.dump(vars)
  File "/usr/local/lib/python3.12/site-packages/psycopg/_queries.py", line 223, in dump
    self.params = tuple(
                  ^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/_queries.py", line 224, in <genexpr>
    self._tx.as_literal(p) if p is not None else b"NULL" for p in params
    ^^^^^^^^^^^^^^^^^^^^^^
  File "psycopg_binary/_psycopg/transform.pyx", line 206, in psycopg_binary._psycopg.Transformer.as_literal
  File "psycopg_binary/_psycopg/transform.pyx", line 215, in psycopg_binary._psycopg.Transformer.as_literal
  File "/usr/local/lib/python3.12/site-packages/psycopg/adapt.py", line 57, in quote
    value = self.dump(obj)
            ^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/types/json.py", line 151, in dump
    data = dumps(obj)
           ^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
          ^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
           ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/core/serializers/json.py", line 106, in default
    return super().default(o)
           ^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
**TypeError: Object of type ManyRelatedManager is not JSON serializable**

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/local/lib/python3.12/site-packages/django/db/models/base.py", line 814, in save
    self.save_base(
  File "/usr/local/lib/python3.12/site-packages/django/db/models/base.py", line 861, in save_base
    pre_save.send(
  File "/usr/local/lib/python3.12/site-packages/django/dispatch/dispatcher.py", line 177, in send
    (receiver, receiver(signal=self, sender=sender, **named))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  **_File "/usr/local/lib/python3.12/site-packages/approval/listeners/monitored.py", line 37, in before_save
    instance.approval._update_sandbox()
  File "/usr/local/lib/python3.12/site-packages/approval/models/monitoring.py", line 215, in _update_sandbox
    self.save()
  File "/usr/local/lib/python3.12/site-packages/approval/models/monitoring.py", line 117, in save
    return super().save(**options)
           ^^^^^^^^^^^^^^^^^^^^^^^_**
  File "/usr/local/lib/python3.12/site-packages/django/db/models/base.py", line 814, in save
    self.save_base(
  File "/usr/local/lib/python3.12/site-packages/django/db/models/base.py", line 877, in save_base
    updated = self._save_table(
              ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/models/base.py", line 1020, in _save_table
    results = self._do_insert(
              ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/models/base.py", line 1061, in _do_insert
    return manager._insert(
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/models/manager.py", line 87, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/models/query.py", line 1805, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/models/sql/compiler.py", line 1822, in execute_sql
    cursor.execute(sql, params)
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 101, in execute
    with self.debug_sql(sql, params, use_last_executed_query=True):
  File "/usr/local/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/utils.py", line 119, in debug_sql
    sql = self.db.ops.last_executed_query(self.cursor, sql, params)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/postgresql/operations.py", line 301, in last_executed_query
    return self.compose_sql(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/postgresql/operations.py", line 194, in compose_sql
    return mogrify(sql, params, self.connection)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/db/backends/postgresql/psycopg_any.py", line 22, in mogrify
    return ClientCursor(cursor.connection).mogrify(sql, params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/client_cursor.py", line 40, in mogrify
    pgq = self._convert_query(query, params)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/client_cursor.py", line 79, in _convert_query
    pgq.convert(query, params)
  File "/usr/local/lib/python3.12/site-packages/psycopg/_queries.py", line 213, in convert
    self.dump(vars)
  File "/usr/local/lib/python3.12/site-packages/psycopg/_queries.py", line 223, in dump
    self.params = tuple(
                  ^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/_queries.py", line 224, in <genexpr>
    self._tx.as_literal(p) if p is not None else b"NULL" for p in params
    ^^^^^^^^^^^^^^^^^^^^^^
  File "psycopg_binary/_psycopg/transform.pyx", line 206, in psycopg_binary._psycopg.Transformer.as_literal
  File "psycopg_binary/_psycopg/transform.pyx", line 215, in psycopg_binary._psycopg.Transformer.as_literal
  File "/usr/local/lib/python3.12/site-packages/psycopg/adapt.py", line 57, in quote
    value = self.dump(obj)
            ^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/psycopg/types/json.py", line 151, in dump
    data = dumps(obj)
           ^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
          ^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/encoder.py", line 258, in iterencode
    return _iterencode(o, 0)
           ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/django/core/serializers/json.py", line 106, in default
    return super().default(o)
           ^^^^^^^^^^^^^^^^^^
  **File "/usr/local/lib/python3.12/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type ManyRelatedManager is not JSON serializable**

I'm unsure if this is an error that others are facing when trying to monitor models with m2m relations.
Relevant contents of my Pipfile:

django = "==4.2.11"
psycopg = {extras = ["binary"] }
django-approval = "*"

Looks like the save() method in the DynamicSandbox class might be the culprit.

Is there a way to add a check to see if the model has any attributes that are m2m and if so, pass those fields off to separate methods that serialize and deserialize them by grabbing each related model's pk, storing them in a dictionary one-by-one (maybe changing the key so that it appends '-#' to the end of each key), adding that dictionary of related objects and their PKs into the JSON object, and then doing the reverse in the _update_source method?

Alternatively, if this is a known issue - or if this is something that might be unique to my setup - please let me know.

A fix for the fix

I dont know if you got my other response in the old ticket since its closed so im making a new one just in case:

The change is incorrect. The new if-statement leads to the else block if the model is approved.
And the else block shows an "ImproperlyConfigured" error.
The solution is to move the last check down into the if-statement.

Replace

if hasattr(obj, "approval") and obj.approval and not obj.approval.approved:

with

if hasattr(obj, "approval") and obj.approval: 
    if obj.approval.approved != True:

(like it was in my original solution provided in the first post in this issue)

auto-delete approval when approved

is there a way to automatically delete an approval when it was approved?

OR

is there a way to only show the "This form is showing changes currently pending." warning if the approval has NOT been approved?
currently it shows even if the approval is approved because the code only checks IF there is an approval object but not if its approved or not.

suggested change:
monitored.py line 23:

if hasattr(obj, "approval") and obj.approval:
    if obj.approval.approved != True:                // added check

Usage instructions together with Django Restframework?

I guess this library was specifically made to have approvals in Django forms?

Is there a way to

  • send a signal to approve a model
  • instead of using is_visible to allow for custom logic for approval e.g. change enum from AWAITING_APPROVAL to PENDING

Thanks for making this library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.