RamezIssac / django-slick-reporting

The Reporting Engine for Django. Create dashboards and standalone Reports and Charts.
https://django-slick-reporting.com/
BSD 3-Clause "New" or "Revised" License
548 stars 44 forks source link

Basic Reports 'Form' object has no attribute 'get_start_date' and DataTables warning: #86

Open Parthian opened 12 months ago

Parthian commented 12 months ago

Trying some basic reports with django-slick-reporting (1.1.0) a ListReportView

class EmployeePPEOwnerReport(ListReportView):
    report_model = PPEOwner
    report_title = "Employee PPEOwner Report"
    date_field = 'date_assigned'
    #group_by = 'employee' # FK to base calculations on
    columns = [
     'date_assigned', 'attachment' # doesn't matter what columns I try.
    ]
urls.py
path("employeeppereport/",EmployeePPEOwnerReport.as_view()),

This returns 
AttributeError at /employeeppereport/
'Form' object has no attribute 'get_start_date'

Then an oddity with group_by. Two Reports. The first works but needs to have the group_by model fields for anything to appear. The second report fails to compile with the error message hinting that group_by has changed the report_model.

class EmployeeLocReport(ReportView):
    report_model = Employee
    report_title = "Employee Location Report - need to use Location fields! "
    date_field = "dob"
    group_by = "location" # this is a FK in Employee
    columns = [ # these columns are all Location fields. 
        'location_name', 'status', 'sign', 'location_code'
    ]

class EmployeeFieldsReport(ReportView):
    report_model = Employee
    report_title = "Employee Fields in Report as Expected but fails to compile"
    date_field = "dob"
    group_by = "location" # this is a FK in Employee
    # Field "firstname" not found either as an attribute to the generator class 
    # <class 'humanresources.reports.EmployeeFieldsReport'>, 
    # Container class <class 'humanresources.reports.EmployeeFieldsReport'>,
    # or a computation field, 
    # or a database column for the model "<class 'humanresources.models.Location'>"    
    columns = [ # these columns are all Employee but don't work fields. 
        'firstname', 'surname', 'dob', 'location'
    ]

This looks similar to the closed issue https://github.com/ra-systems/django-slick-reporting/issues/58
Jan29 comment - "Note that both customer and customer__origin have a company_name field, but the ReportView tries to read that field on the report_model instead of the group_by model." I seem to have the opposite. ReportView is reading fields from group-by not report_model.

I may have misunderstood group_by.

Thanks.

Parthian commented 11 months ago

Figured out group_by. That it exposes the FK fields rather than report_model.

But still having trouble with a simple ListReportView E.g.

class EmployeeGenderReport(ListReportView): 
    report_model = Employee
    report_title = "Employee Gender Report"
    columns = [ 'surname', 'firstname', 'gender' ]

Results in Errors in uwsgi.log

Could not resolve form field 'start_date'.
Traceback (most recent call last):
  File "/home/username/apps/appname/env/lib/python3.10/site-packages/django/forms/forms.py", line 178, in __getitem__
    field = self.fields[name]
KeyError: 'start_date'

KeyError: "Key 'start_date' not found in 'Form'. Choices are: ."
Could not resolve form field 'end_date'.
Traceback (most recent call last):
  File "/home/username/apps/appname/env/lib/python3.10/site-packages/django/forms/forms.py", line 178, in __getitem__
    field = self.fields[name]
KeyError: 'end_date'

During handling of the above exception, another exception occurred:

  File "/home/username/apps/appname/env/lib/python3.10/site-packages/slick_reporting/views.py", line 480, in get_report_generator
    start_date=self.form.get_start_date(),
AttributeError: 'Form' object has no attribute 'get_start_date'

Upset by a lack of dates. Tried adding start_date and end_date to the View but no impact.

Related. All my Reports start at the beginning of the year. image Tried start_date = '1900-01-01 00:00:00' but that has no impact.

Thanks. Using 1.1.1

RamezIssac commented 11 months ago

Thank you for the update and the report

There is a fix in the develop branch just made today for the initials.

You should be able to set your initials via

  1. the initial attribute
  2. or overriding the get_initial() on the ReportView

Also,Check slick reporting settings here https://django-slick-reporting.readthedocs.io/en/latest/ref/settings.html to change the default value of your dates.

Parthian commented 11 months ago

Updated the settings and no change. Which is probably correct for 1.1.1 pre your fix. What is the best pip command to update to develop version. I remember I tried 3 different ways before and none worked. Very odd.

Parthian commented 11 months ago

Updated to develop version by copying files (seems wrong). My reports no longer work (some were suspect to start). This was a bit swish showing the number of people in each age range. Using dob (Date of Birth) as Count is a bit random as it is really a Count of how many entries are in the queryset.

def age_range(min_age, max_age):
    current = now().date()
    min_date = date(current.year - min_age, current.month, current.day)
    max_date = date(current.year - max_age, current.month, current.day)
    return Employee.objects.filter(dob__gte=max_date,
                                dob__lte=min_date).order_by("dob")

class EmployeeDemographic(ReportView):
    report_model = Employee
    report_title = "Employee Demographic Report"
    date_field = 'dob'
    start_date = '1900-01-01 00:00:00'
    # group_by = 'location'

    group_by_custom_querysets = [
        age_range(0,30),
        age_range(31,40),
        age_range(41,50),
        age_range(51,70)
    ]
    group_by_custom_querysets_column_verbose_name = _("Date Range Verbose")
    # ComputationField below was SlickReportField
    columns = [
        "__index__", 
        # can't use these as rows are aggregated. "surname", "location__location_name",
        ComputationField.create(method=Count, field="dob", name="age", verbose_name="Age Range", is_summable=False,),
    ]
    #default_order_by = "-doc_date"
    limit_records = 20
    auto_load = True # False if demanding. DOESN'T SEEM TO WORK 
    chart_settings = [
        Chart(
            _("Age Chart"),
            Chart.BAR,
            data_source=["age"],
            title_source=["__index__"]
        ),
    ]

    def format_row(self, row_obj):
        # Put the verbose names we need instead of the integer index
        index = row_obj['__index__']
        if index == 0:
            row_obj["__index__"] = "0,30"
        elif index == 1:
            row_obj['__index__'] = "31,40"
        elif index == 2:
            row_obj['__index__'] = "41,50"
        elif index == 3:
            row_obj['__index__'] = "51,70"
        return row_obj

uwsgi.log ends

  File "/home/username/apps/appname/env/lib/python3.10/site-packages/slick_reporting/generator.py", line 540, in <listcomp>
    data = [format_row(get_record_data(obj, all_columns)) for obj in main_queryset]
  File "/home/username/apps/appname/env/lib/python3.10/site-packages/slick_reporting/generator.py", line 520, in _get_record_data
    value = computation_class.do_resolve(group_by_val, data)
AttributeError: 'ReportField_age' object has no attribute 'do_resolve'
[pid: 210510|app: 0|req: 2/2] 127.0.0.1 () {62 vars in 1341 bytes} [Fri Oct  6 11:35:39 2023] GET /employeedemographic/?start_date=2000-01-01+00%3A00%3A00&end_date=2023-10-06+11%3A35%3A25 => generated 145 bytes in 1056 msecs (HTTP/1.1 500) 8 headers in 288 bytes (1 switches on core 1)

ReportField_age refers to my ComputedField name of 'age'. Note also that auto_load may not be working. I only get the 500 error after filtering or changing the date forces a refresh.

Parthian commented 11 months ago

I had overridden the base.html to change title tag and add some inline css. The develop version has this. {% block extrajs %} {% include "slick_reporting/js_resources.html" %} {% endblock %} I've copied that over and I no longer get the 500 error. But my Select2 has gone and my reports no longer work. I think a conflict with my settings.py SLICK_REPORTING_SETTINGS_DEFAULT

Parthian commented 11 months ago

I've added this to my reports. Works. From date is now 2001. Super. Reports still broken from this morning.

    def get_initial(self):
        initial = super().get_initial()
        initial["start_date"] = '2001-01-01 00:00:00'
        return initial
RamezIssac commented 11 months ago

I will look into that and get back to you asap

RamezIssac commented 11 months ago
  1. To setup from develop branch pip install -e -e git+https://github.com/ra-systems/django-slick-reporting.git@develop#egg=django-slick-reporting

  2. "'ReportField_age' object has no attribute 'do_resolve'" can you share the ReprotField_age structure?

  3. auto_load issue : iwill check it... but did you remember to collectstatic after upgrading ?!

  4. SLICK_REPORTING_SETTINGS_DEFAULT: It's SLICK_REPORTING_SETTINGS (there is no _DEFAULT)

Parthian commented 11 months ago

1) The new pip runs - magical. Still states it is 1.1.1 but that's possibly due to not being an official release. My Reports still don't work as they were this morning [update - see 3)] with the official 1.1.1 - note you've a typo with an extra -e. These egg installs didn't work for me before (probably ERP). Never sussed why. Can't find my notes, possibly an email.

2) Age is shown above a name for a ComputeField just counting the number of dob entries. A bit daft but acts as a count of members of each age_range custom queryset. This was working and showing the correct number of people in each age range. But my silly mistake with base.html that I'd overridden was the solution to that particular do_resolve thing. Please ignore.

3) Jackpot. I'm sure I did a collectstatic earlier today but another one just after the proper pip install moved 5 more files over. And my Report now works. Select2 now working as well. My Swish Bar Graph showing the number of people in each age range is back. Awesome.

I assume in my settings I can just add the dictionary settings I want to change. e.g.

SLICK_REPORTING_SETTINGS = {
    "DEFAULT_START_DATE_TIME": '1999-01-01 00:00:00',
    "DEFAULT_END_DATE_TIME": datetime.datetime.today(),  # today
}

The coded get_initial is really handy as one start_date for all Reports isn't handy. So this way I've a nice default and a handy override for any Reports that need a different timescale. Super. I didn't understand your earlier option You should be able to set your initials via 1) the initial attribute

  1. For reports without a get_initial my chosen default of 1999 appears as expected. Great.

Thanks so much. It must be late in Egypt.

RamezIssac commented 11 months ago
class MyReport(ReportView):
    initial = {} # it's a CBV option and it's now integrated, `get_initial` is more powerful of course.

My pleasure

Parthian commented 11 months ago

All good. The bug fixes and my brain getting around some of the concepts is leading to positive results after a bit of a battle.

I think the biggest problem I've had is group_by with a FK results in only fields from the FK model being available. Took a while for that to sink in. It is documented but it seems odd and kept catching me out. Of course, it is an aggregation thing. No good getting the names for all Employees if they are lumped into one row for a ComputeField.

And maybe you could add another Tutorial example but without the focus on Sales and Values? You may find a lot of people will be interested in Slick Reporting but not for Sales tracking. Could make it easier for folks.

Now to figure out a Time Series and Crosstab solution. But not today.

RamezIssac commented 11 months ago

And maybe you could add another Tutorial example but without the focus on Sales and Values? You may find a lot of people will be interested in Slick Reporting but not for Sales tracking. Could make it easier for folks.

I understand, What kind of use case you would find clearer to put in a tutorial ?

Time Series and crosstab will be pieace of cake ;-) Follow with the docs and i'm always happy to receive your feedback .

Cheers

Parthian commented 11 months ago

I think a good Tutorial would be something classic like Person. Very similar to my Employee. And could be easily re-worked into other database classics like People - Student - Staff. Or Django's project docs favourite of Author Books. Where date would be Date of Birth or date a Person joined the School either as Staff or Student.

I've got a nice pie chart of genders using.

models.py
    class genderOptions(models.TextChoices):
        MALE = 'Male', _('Male')
        FEMALE = 'Female', _('Female')
....
    gender = models.CharField(_('Gender'), max_length=32, choices=genderOptions.choices, default=genderOptions.MALE )
....
reports.py (ReportView)

 group_by_custom_querysets = [
        Employee.objects.filter(gender='Male'),
        Employee.objects.filter(gender='Female'),
    ]
    columns = [  "__index__",  ComputationField.create(method=Count, field='gender', name='gender_count', verbose_name=_('Count'))
    ]
    chart_settings = [
        Chart(
            _("Gender Mix"),
            Chart.PIE,
            data_source=["gender_count"],
            title_source=["__index__"]
        ),
    ]
    def format_row(self, row_obj):
        # Put the verbose names we need instead of the integer index
        index = row_obj['__index__']
        if index == 0:
            row_obj["__index__"] = "Male"
        elif index == 1:
            row_obj['__index__'] = "Female"
        return row_obj

A question. The above is neat for 2-4 options. But after that it gets a bit cumbersome. The gender model type is a ChoiceField. I've another field where there are 10+ entries in the ChoiceField. With the above approach I'd have to have all 10 entries in the group_by_custom_querysets and in the format_row.

The docs suggest that group_by can work with Text but I've not had any success. So could the above work with group_by = "gender"?

Parthian commented 11 months ago

UPDATE. I can get ChoiceFields to work with group_by. I think the problem I had was my usual of trying to show columns that can't be shown - usually the fields from the group_by model but in this case I think only the field itself plus the calculation can be a column. Does that make sense?

RamezIssac commented 11 months ago

Here is an example of what goes into the columns atttribute of the ReportView / Generator API

class Report(ReprotView):
    columns = ["field_on_group_by_model", "group_by_model__traversing_field",  "get_attribute", ComputationField.create(name="example")]

    def get_attribute(self, obj: dict, row: dict) -> any:
         # obj: a dictionary of the current group_by row 
         # row: a the current row of the report.
        return f"{obj["field_on_group_by_model_2"]} - {row["group_by_model__traversing_field"]}"

    get_attribute.verbose_name = "My awesome title"

Hope this helps , not sure where the documentation for that went... will find a place to add it

Parthian commented 11 months ago

The initial error away at the top:

class EmployeePPEOwnerReport(ListReportView):
    report_model = PPEOwner
    report_title = "Employee PPEOwner Report"
    date_field = 'date_assigned'
    columns = ['date_assigned', 'attachment' # doesn't matter what columns I try.]

Loads of errors about start_date and end_date.

It's due to having a date_field on a ListReportView. I usually end up leaving the date_field as I blunder around trying to fix things. Sometimes switching from ReportView to ListReportView and getting more confused.

Could you update the ListReportView doc https://django-slick-reporting.readthedocs.io/en/latest/topics/list_report_options.html#list-reports to state not to use a date_field. It doesn't show one but an easy thing to miss for a newbie.

RamezIssac commented 11 months ago

Than you ... i will check and update

RamezIssac commented 11 months ago

@Parthian not sure what you mean about using date field, it does not have an effect , I can not reproduce any errors . Can you elaborate ?

RamezIssac commented 11 months ago

Now on develop, date_field only need to b set for time series. for less issues with date field

oe1rsa commented 4 weeks ago

I ran into the same error / warning. Just from a fresh clone from github, then switched to master and checkout out tag v1.3.1 (to reflect PyPi).

When running the "last 10 sales" report I get the following in the console where I started by ./manage.py runserver:

./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
August 18, 2024 - 14:58:43
Django version 5.1, using settings 'demo_proj.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

WARNING:root:Could not resolve form field 'start_date'.
Traceback (most recent call last):
  File "/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/django/forms/forms.py", line 174, in __getitem__
    field = self.fields[name]
KeyError: 'start_date'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/crispy_forms/utils.py", line 69, in render_field
    bound_field = form[field]
  File "/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/django/forms/forms.py", line 176, in __getitem__
    raise KeyError(
KeyError: "Key 'start_date' not found in 'SalesTransactionForm'. Choices are: client, date, product."
WARNING:root:Could not resolve form field 'end_date'.
Traceback (most recent call last):
  File "/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/django/forms/forms.py", line 174, in __getitem__
    field = self.fields[name]
KeyError: 'end_date'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/crispy_forms/utils.py", line 69, in render_field
    bound_field = form[field]
  File "/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/django/forms/forms.py", line 176, in __getitem__
    raise KeyError(
KeyError: "Key 'end_date' not found in 'SalesTransactionForm'. Choices are: client, date, product."
[18/Aug/2024 14:58:50] "GET /last-10-sales/ HTTP/1.1" 200 22708
/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/django/db/models/fields/__init__.py:1665: RuntimeWarning: DateTimeField SalesTransaction.date received a naive datetime (2024-01-01 00:00:00) while time zone support is active.
  warnings.warn(
/home/roland/.virtualenvs/django-slick-reporting/lib/python3.10/site-packages/django/db/models/fields/__init__.py:1665: RuntimeWarning: DateTimeField SalesTransaction.date received a naive datetime (2024-08-18 14:58:50.359619) while time zone support is active.
  warnings.warn(
[18/Aug/2024 14:58:50] "GET /last-10-sales/?product=&client=&date= HTTP/1.1" 200 1704

Ok, it says WARNING but an exception during another exception does not sound like a warning to me. Also the date-from and date_to buttons are missing. I believe that is what the original poster tried to point out.

So, does this mean ListReportViews do have a bug?

Thank you for considering.