Django - remove duplicates records from DB - django

I want to set 'unique_together' on my DB (postgres). The problem is that I may already have duplicates on DB, so migration would probably not work. So as I see it - before the deployment I need to run some script to remove all duplications (leave only one of each). I prefer doing it with Django Custom Command.
The table 'mapping' looks something like - Id, user_id, user_role, project_id, user_type.
I want to set 'unique_together' for all of them.
And the script I use to retrieve duplicated rows is-
duplicates = (Mapping.objects.values('project_id', 'user_id', 'user_type', 'user_role').annotate(
count=Count('id')).values('project_id', 'user_id', 'user_type', 'user_role').order_by().
filter(count__gt=1))
It returns list of objects that contains the duplicated attributes.
for example:
QuerySet [{'user_id': '2222', 'user_type': '1', 'user_role': '1', 'project_id': UUID('c02bda0e-5488-4519-8f34-96b7f3d36fd6')}, {'user_id': '44444', 'user_type': '1', 'user_role': '1', 'project_id': UUID('8c088f57-ad0c-411b-bc2f-398972872324')}]>
Is there a way to retrieve the Ids directly?
Is there a better way?

You can try it:
Mapping.objects.values(
'project_id', 'user_id', 'user_type', 'user_role'
).annotate(count=Count('id')
).annotate(max_id=Max('id')
).values('max_id').order_by().filter(count__gt=1)

Related

Django form - the same field multiple times

how can I process a form with a field:
order = ModelChoiceField(
required=False,
queryset=OrderOd.objects.filter(Q(status='DN') | Q(status='DI')),
widget=Select(
attrs={
"class": "form-select form-select-md form-select-solid",
"data-control": "select2",
"data-multiple": "true",
"multiple": "multiple",
"data-placeholder": _("Vyberte objednávku ..."),
"id": 'order'
}
)
)
In front-end, I can select multiple orders (looks like pills/tags) and in the request sent to the server it looks like this:
movement: f2b7c234-fbdb-4059-bcb6-8ada46cef72c
account: dbabefb7-f053-4edf-a2e3-787bf6bfc371
date: 2022-09-12
order: eb2fc726-3e97-4af2-a8b2-08f20771cfef
order: 8398925b-fca6-4b25-8c48-e12940a5b5c3
order: bfa35391-5cf8-4ed8-8c44-a797da875cb4
order: 07be93ac-20b3-459c-8038-c8b023db6d66
When I inspect self.data, I got
'order': ['eb2fc726-3e97-4af2-a8b2-08f20771cfef', '8398925b-fca6-4b25-8c48-e12940a5b5c3', 'bfa35391-5cf8-4ed8-8c44-a797da875cb4', '07be93ac-20b3-459c-8038-c8b023db6d66'],
but when I check the output of logger.info(self.data['order']), it gives me only the first UUID.
[INFO] form.py 123: 07be93ac-20b3-459c-8038-c8b023db6d66
What I need is to access all UUIDs in the array (order) and process them instance by instance.
Any idea, how to do it?
Thanks
You can use self.data.getlist('order') to return the data in the array form.
see more info in Django documentation

objects.update_or_create() creates a new record in database rathe than update exisiting record

I have created a model in Django and for which I get the data from an API. I am trying to use the update_or_create method for getting the data from the API and into my database. However, I may be confused on how it works.
When I run it for the first time it adds the data into the database as expected, however if I were to update one of the fields for a record - the data from the API has changed for the respective record - and then run it again, it is creating a new record rather than updating the existing record.
So in the scenario below, I run it and the count for record Commander Legends is 718, which I then update manually in the database to be 100. When I run it again, it creates a new record with count of 718
With my understanding of it, it should of updated the record rather than create a new record.
views.py
def set_update(request):
try:
discover_api = requests.get('https://api.scryfall.com/sets').json()
set_data = discover_api['data']
while discover_api['has_more']:
discover_api = requests.get(discover_api['next_page']).json()
set_data.extend(discover_api['data'])
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}')
except Exception as err:
print(f'Other error occurred: {err}')
sorted_data = sorted(set_data, key=lambda k: k['releaseDate'], reverse=False)
for i in sorted_data:
Set.objects.update_or_create(
scry_id=i.get('id'),
code=i.get('code'),
name=i.get('name'),
type=i.get('set_type').replace("_", " ").title(),
release_date=i.get('released_at'),
card_count=i.get('card_count'),
is_digital_only=i.get('digital', False),
is_non_foil_only=i.get('nonfoil_only', False),
is_foil_only=i.get('foil_only', False),
block_name=i.get('block'),
block_code=i.get('block_code'),
parent_set_code=i.get('parent_set_code'),
tcgplayer_id=i.get('tcgplayer_id'),
last_modified=date.today(),
defaults={
'scry_id': i.get('id'),
'code': i.get('code'),
'name': i.get('name'),
'type': i.get('set_type').replace("_", " ").title(),
'release_date': i.get('released_at'),
'card_count': i.get('card_count'),
'is_digital_only': i.get('digital', False),
'is_non_foil_only': i.get('nonfoil_only', False),
'is_foil_only': i.get('foil_only', False),
'block_name': i.get('block'),
'block_code': i.get('block_code'),
'parent_set_code': i.get('parent_set_code'),
'tcgplayer_id': i.get('tcgplayer_id'),
'last_modified': date.today(),
}
)
return redirect('dashboard:sets')
Screenshot
In a update_or_create(…) [Django-doc], you have basically two parts:
the named parameters which do the filtering, only if they can find a record that matches all the filters, it will update that record; and
the defaults=… parameter, which is a dictionary of values that will be used to update or create that record.
If you thus want to update only the card_count and last_modified, it looks like:
Set.objects.update_or_create(
scry_id=i.get('id'),
code=i.get('code'),
name=i.get('name'),
type=i.get('set_type').replace('_', ' ').title(),
release_date=i.get('released_at'),
is_digital_only=i.get('digital', False),
is_non_foil_only=i.get('nonfoil_only', False),
is_foil_only=i.get('foil_only', False),
block_name=i.get('block'),
block_code=i.get('block_code'),
parent_set_code=i.get('parent_set_code'),
tcgplayer_id=i.get('tcgplayer_id'),
defaults={
'card_count': i.get('card_count'),
'last_modified': date.today()
}
)
If you thus add card_count at the kwargs, it will only update the record if the card_count matches completely, so if both defaults and the kwargs contain the same values, you basically will either do nothing in case a record exists with all the values in place, or create a new one if no such record is present.

How to exclude submission values from saving?

Is there a way to exclude certain submission values from being saved to the submissions table in Drupal? I would like to send a complete set of all submission values per submission via email, but I would like to exclude personal data like email-addresses and the like from being saved to the table. Is there a way to accomplish that?
You can use Drupal's webform submission exporter service to export submission data and also exclude unwanted columns during export. Something like this:
$submission_exporter = \Drupal::service('webform_submission.exporter');
$export_options = $submission_exporter->getDefaultExportOptions();
$export_options['excluded_columns'] = [
'uuid' => 'uuid',
'token' => 'token',
'webform_id' => 'webform_id',
'completed' => 'completed',
];
$submission_exporter->setWebform($webform);
$submission_exporter->setExporter($export_options);
$submission_exporter->generate();
$temp_file_path = $submission_exporter->getExportFilePath();

Django - POST - save multiple items in queryset

I'm not sure if the title is correctly describing the issue. If you have a more descriptive title, please edit the title.
I have a following dynamically added input fields:
The first two rows is the first object, second row is the second object, and so on.... I am trying to save these dynamically added elements to DB by customizing post() method.
Using my current version of overriding post() method:
if 'bha' in request.POST:
bha_form = BHA_Form(request.POST, instance= #some_kwargs...)
print(request.POST)
it prints the following queryset:
<QueryDict: {'csrfmiddlewaretoken': [# foo_token], 'item_description': ['aaaaaaaaa', 'bbbbbbbbb'], 'num_of_jts': ['aaaaaaaaaaa', 'bbbbbbbbb'], 'length': ['aaaaaaaaaaaa', 'bbbbbbbbbb'], 'cum_length': ['aaaaaaaaaaaaa', 'bbbbbbbbbbbbbb'], 'outer_d': ['aaaaaaaaaaaaa', 'bbbbbbbbbbbbbbb'], 'inner_d': ['', ''], 'drift_d': ['', ''], 'od_cplg': ['', ''], 'top_thread': ['', ''], 'air_wght': ['', ''], 'make': ['', ''], 'sn': ['', ''], 'bha_component': ['Submit']}>
As you can see, each input fields have 2 values in a form of list, because there are two objects present on the Front-End. If I just use bha_form.save(), it will save only the last value in a queryset list. This is not what I want. I want to use something like get_or_create() method to create new objects for each dynamically added elements, or edit objects if they are already present in DB
How should I do this? I guess I can write some long set of codes to get this done, but just curious if there's any elegant way of achieving this.
You can use Django Formsets that do exactly what you're asking for : posting many objects at the same time. It is a very clean way to do so.
The documentation in the link above explains how to use it in details.

Plone: The number of fields in the new order differs from the number of fields in the schema

I have a batch.py file which have many fields in it. So i am Trying to add a new ReferenceField in batch.py as per my requirement.
ExtReferenceField('Asfield',
required = 0,
multiValued=1,
allowed_types = ('AnalysisService',),
referenceClass = HoldingReference,
relationship = 'BatchAsfield',
widget=SearchAnalysisWidget(
label=_("AS Search"),
description="",
render_own_label=False,
visible={'edit': 'visible', 'view': 'visible'},
#visible=True,
base_query={'inactive_state': 'active'},
catalog_name='portal_catalog',
showOn=True,
colModel = [{'columnName':'AsCode','width':'20','label':_('Code')},
{'columnName':'AsName','width':'80','label':_('Name')},
{'columnName':'AsDate','width':'80','label':_('Date')},
{'columnName':'AsTat','width':'80','label':_('TAT')},
{'columnName':'AsLocation','width':'80','label':_('Location')},
],
),
),
It gives me error like:
ValueError: The number of fields in the new order differs from the
number of fields in the schema.
I get the Solution for this error. This error is coming because i am not added my Asfield in the getOrder method of batch.py file.
Actually our widgets needs a particular order to show at browser.So by using the getOrder method we can maintain the order of all widgets(fields) at browser. This getOrder is defined in same file batch.py.
def getOrder(self, schematas):
schematas['default'] = ['id',
'title',
'description',
'BatchID',
'ClientPatientID',
'Patient',
'Client',
'Doctor',
'Asfield',
'ClientBatchID',
'ReceiptNo',
'AmountPaid',
'BatchDate',
'OnsetDate',
'PatientAgeAtCaseOnsetDate',
]
return schematas
I just add Asfield (ReferenceField) after the Doctor (at the browser this Referencewidget will show just after Doctor). You can add your widget where you want it.
It is simple Solution for this value error.