Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate sample's DateOfBirth field to AgeDateOfBirthField #74

Merged
merged 21 commits into from
May 18, 2023
Merged

Conversation

xispa
Copy link
Member

@xispa xispa commented May 16, 2023

Description of the issue/feature this PR addresses

Warning
Merge senaite/senaite.core#2310 and senaite/senaite.core#2311 first

This Pull Request makes senaite.patient compatible with senaite/senaite.core#2307 and senaite/senaite.core#2311 by replacing the DateTimeField type for sample's DateOfBirth to a new field AgeDateOfBirthField, fully supported by the AgeDoBWidget.

Current behavior before PR

The "DateOfBirth" field was only storing the datetime, while other attributes required for the field and widget to work properly were stored as independent attributes in the instance itself.

Desired behavior after PR is merged

The "DateOfBirth" field stores a tuple (dob, from_age, estimated).

--
I confirm I have tested this PR thoroughly and coded it according to PEP8
and Plone's Python styleguide standards.

@xispa xispa requested a review from ramonski May 16, 2023 16:53
@xispa xispa marked this pull request as draft May 17, 2023 07:50
@xispa xispa marked this pull request as ready for review May 17, 2023 17:44
Copy link
Contributor

@ramonski ramonski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phew, what a brain excursion... I left some comments on my journey through the code.

@@ -186,6 +188,7 @@ def update_patient(patient, **values):
patient.reindexObject()


@deprecated("Us senaite.core.api.dtime.to_dt instead")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small typo: Us -> Use

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved with c06ae2b

if not any([years, months, days]):
raise AttributeError("No valid ymd: {}".format(age_ymd))
if default is _marker:
raise AttributeError("No valid ymd: {}".format(ymd))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use a TypeError or ValueError instead of a AttributeError. But probably not needed at all if we just return (0, 0, 0).


ymd = list("ymd")
diff = map(str, (delta.years, delta.months, delta.days))
diff = map(str, (val.years, val.months, val.days))
age = filter(lambda it: int(it[0]), zip(diff, ymd))
return " ".join(map("".join, age))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Man, you drive me nuts with constructs like this 🙈

Just to understand:
You are converting the years, months and days to strings. Then you zip them together with ["y", "m", "d"] to filter out empties, e.g. to get 5y2d to finally " ".join(map("".join, age)), which returns 5y2d again?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've seen it in the doctest now, it generates something like 43y 2m 3d.
As proposed further down, please encapsulate it in an own function, e.g. format_ymd to do this.

valid = map(lambda p: p in ymd, "ymd")
return any(valid)
values = get_years_months_days(ymd, default=None)
return values is not None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop the default handling (also below) in favor of this construct:

def is_ymd(ymd):
    try:
        get_years_months_days(ymd)
    except TypeError:
        return False
    return True

def get_birth_date(age_ymd, on_date=None):
"""Returns the birth date given an age in ymd format and the date when age
was recorded or current datetime if None
def get_years_months_days(ymd, default=_marker):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the default handling for simplicity (only used in is_ymd and get_birth_date)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

days = extract_period(age_ymd, "d")
years = extract_period(ymd, "y")
months = extract_period(ymd, "m")
days = extract_period(ymd, "d")
if not any([years, months, days]):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we just return (0, 0, 0)?
E.g. for a new born baby with 2 hours.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, with latest changes it always returns (0, 0, 0) unless format or type are not valid

years, months, days = get_years_months_days(ymd)
except AttributeError:
if default is _marker:
raise AttributeError("No valid ymd: {}".format(age_ymd))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phew, catch an AttributeError (which is actually none) to raise an AttributeError again to (probably) bypass the IndexError that might happen here years, months, days = get_years_months_days(ymd) ...

I would maybe completely skip the handling here and leave it to the get_birth_date to either raise a TypeError or return (0, 0, 0).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified the whole thing and now it raises TypeError or ValueError only if the format is not correct

delta = dtime.get_relative_delta(birth_date, on_date)
return to_ymd(delta)
except (ValueError, TypeError):
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do everywhere the default handling except of here you return None? What happened? 😅

Maybe you can skip here the ValueError. At least I see only the explicit TypeError that is raised by to_ymd.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

ymd = map(lambda p: value.get(p), ["years", "months", "days"])
if not any(ymd):
# No values set
return None
return None, {}

# Age in ymd format
ymd = filter(lambda p: p[0], zip(ymd, 'ymd'))
ymd = "".join(map(lambda p: "".join(p), ymd))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, once again this dance. If it is really required, I would somehow encapsulate it in a format_ymd function with a good docstring / comments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with e77df12 . Is now using to to_ymd function

super(AgeDateOfBirthField, self).set(instance, val)

def get_date_of_birth(self, instance):
"""Returns whether the date of birth
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incomplete/confusing docstring

@xispa
Copy link
Member Author

xispa commented May 18, 2023

Phew, what a brain excursion... I left some comments on my journey through the code.

Hehe I think so. Apologies for these excursions


>>> dob = dtime.to_dt("19791207")
>>> api.get_age_ymd(dob, on_date="20230518")
'43y 5m 11d'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy birthday:)

@ramonski ramonski merged commit 113c974 into master May 18, 2023
@ramonski ramonski deleted the age-field branch May 18, 2023 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants