Doctrine: Automatic ResultMapping of Native Query? - doctrine-orm

After some research, I made this UNION query work in my Repository class:
class PostRepository extends ServiceEntityRepository {
// ...
public function getLatestPostsOfUser ($limit = 10) : ?array {
$sql = <<<SQL
SELECT p.id, p.title, p.content, p.images, p.parent_id, p.parent_type, p.created, p.last_modified FROM cms_posts p
LEFT JOIN cms_user_follow ON (p.parent_type = cms_user_follow.followed_entity_type AND p.parent_id = cms_user_follow.followed_entity_id)
WHERE cms_user_follow.user_id = {$this->currentUser->getId()}
UNION
SELECT p.id, p.title, p.content, p.images, p.parent_id, p.parent_type, p.created, p.last_modified FROM cms_posts p
LEFT JOIN project_memberships ON (p.parent_type = 'Project' AND p.parent_id = project_memberships.project_id)
WHERE project_memberships.user_id = {$this->currentUser->getId()} and project_memberships.status = 1
ORDER BY created DESC
LIMIT $limit
SQL;
$res = [];
try {
$rsm = (new ResultSetMapping())
->addEntityResult(Post::class, 'p')
->addFieldResult('p', 'id', 'id')
->addFieldResult('p', 'title', 'title')
->addFieldResult('p', 'content', 'content')
->addFieldResult('p', 'images', 'images')
->addFieldResult('p', 'parent_id', 'parentId')
->addFieldResult('p', 'parent_type', 'parentType')
->addFieldResult('p', 'created', 'created')
->addFieldResult('p', 'last_modified', 'lastModified')
;
$res = $this->getEntityManager()->createNativeQuery($sql, $rsm)->getArrayResult();
} catch (DBALException $e) {
}
return $res;
}
}
It involves an awefull lot of manual field mapping, so I was wondering wheather there is an automatic solution to this?
Many thx!

It looks like Doctrine can do something like this under the hood and also apply the mappings automatically:
$qb = $this->em->createQueryBuilder();
$where = $qb->expr()->andX(
$qb->expr()->eq('f.followedEntityId', ':parentId'),
$qb->expr()->eq('f.followedEntityType', ':parentType'),
$qb->expr()->eq('f.following', 1),
$qb->expr()->eq('u.allowEmailNotifications', 1),
$qb->expr()->eq('u.enabled', 1),
);
$qb->select('f', 'u')
->from(UserFollow::class, 'f')
->leftJoin(
User::class,
'u',
Join::WITH,
'f.userId = u.id'
)
->where($where)
->setParameters([
'parentId' => $post->getParentId(),
'parentType' => $post->getParentType()
])
;
$usersAndFollowings = $qb->getQuery()->getResult();
$usersAndFollowings is then a flat array with both entities alternating: [UserFollow, User, UserFollow, User, ...]
You'll probably want to process it afterwards, so that connected UserFollow and User entities are together in a sub array.

Related

How to make formula field for salesforce field?

I am trying to make formula field for the salesforce field. the condition is given below.
if Company = "WIL" And (ShippingCountry = "United States" Or "USA") then
"US"
elseif Company = "WST" And (ShippingCountry = "United States" Or "US") then
"USA"
elseif ShippingCountry <> "" then
ShippingCountry
elseif Company = "WIL" then
"US"
elseif Company = "WST" then
"USA"
else
""
end if
The trailheads are always a good start. I would suggest Use Formula Fields and Advanced Formulas.
The documentation pages about Formula Operators and Functions might be useful too.
Keep in mind that you must use fields API Names, not labels, so it's Company__c.
If Company__c is not a picklist field:
IF( AND(Company__c = 'WIL', OR(ShippingCountry = 'United States', ShippingCountry = 'USA')),
'US',
IF( AND(Company__c = 'WST', OR(ShippingCountry = 'United States', ShippingCountry = 'US')),
'USA',
IF( NOT( ISBLANK(ShippingCountry) ),
ShippingCountry,
IF( Company__c = 'WIL',
'US',
IF(Company__c = 'WST', 'USA', '')
)
)
)
)
If Company__c is a picklist field you should use ISPICKVAL(picklist_field, literal_value), so the formula would be:
IF( AND( ISPICKVAL(Company__c, 'WIL'), OR(ShippingCountry = 'United States', ShippingCountry = 'USA')),
'US',
IF( AND(ISPICKVAL(Company__c, 'WST'), OR(ShippingCountry = 'United States', ShippingCountry = 'US')),
'USA',
IF( NOT( ISBLANK(ShippingCountry) ),
ShippingCountry,
IF( ISPICKVAL(Company__c, 'WIL'),
'US',
IF( ISPICKVAL(Company__c, 'WST'), 'USA', '')
)
)
)
)

Django 2 upgrade lost filter_horizontal functionality

I recently upgraded to Django 2.2.2 and Python 3.6.8, and my filter_horizontal feature in Django admin has disappeared.
I tried viewing my admin in Chrome incognito mode, as some answers suggest, and I also tried changing verbose_name strings to unicode. However, none of these worked.
Here is an example of a model for which I am attempting to show filter_horizontal. This worked on my app prior to the upgrades.
admin.py
class ResearchAdmin(admin.ModelAdmin):
filter_horizontal = ('criteria', 'state')
def save_model(self, request, obj, form, change):
obj.save()
# Update cache for tasks containing the saved goal
tasks = Task.objects.filter(research__id=obj.pk).values_list('pk', flat=True)
for t in tasks:
cache.delete_many(['task%s' % t, 'task%s_research' % t])
models.py
"""
Clinical Research model
"""
def __str__(self):
return "%s" % (self.name)
name = models.CharField(max_length=50, null=True, blank=True)
type = models.CharField(max_length=50, null=True, blank=True)
cta = models.CharField(max_length=50, null=True, blank=True)
description = models.TextField(null=True, blank=True)
picture = models.ImageField(upload_to='images/%Y/%m/%d', null=True, blank=True, help_text="Upload portrait image for modal study description")
layout = models.CharField(max_length=1, choices=LAYOUT_TYPE, null=False, blank=False, default='r')
criteria = models.ManyToManyField('Answer', blank=True, db_index=True, help_text="Answers required for qualified patients")
required_contact = models.ManyToManyField('ContactField', blank=True, db_index=True, help_text="Contact info for patient to enter")
email = models.EmailField(null=True, blank=True, help_text="Sponsor email for notifying of screened patients")
link = models.URLField(null=True, blank=True)
state = models.ManyToManyField('State', blank=True, help_text="Qualifying states")
lat = models.CharField(max_length=60, null=True, blank=True)
lng = models.CharField(max_length=60, null=True, blank=True)
distance = models.PositiveIntegerField(null=True, blank=True, help_text="Maximum distance from user in miles to show")
class Meta:
verbose_name = u"Research"
verbose_name_plural = u"Research"
There are no error messages, but the filter_horizontal frontend doesn't show up in admin for the criteria and state fields.
##collectstatic##
As #iain-shelvington suggested, this issue may be due to some kind of interference with the cached front-end code required to display the filter_horizontal format. I have tried running in Google Incognito mode, clearing catch, and running collectstatic --clear, and none of these work. Moreover, there aren't any differences between the admin static files pre- and post- upgrade.
#SylvainBiehler pointed out that django_gulp may be overriding collectstatic. I disabled django_gulp and ran ./manage.py collectstatic --clear all admin files are now updated post Django upgrade.
##Comparing admin files pre- and post- Django upgrade##
I was able to spin up a version of my app pre-Django upgrade, and the filter_horizontal capability works in the older version. There are some differences in the construction of the Criteria field from Chrome console:
Old Version (works)
Select element prior to choices:
<select multiple="multiple" class="selectfilter" id="id_criteria" name="criteria">
Javascript after choices:
<script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_criteria", "criteria", 0, "https://xxxxxxx/static/admin/"); });</script>
New Version (broken)
Slightly different select element. No javascript after choices:
<select name="criteria" id="id_criteria" multiple class="selectfilter" data-field-name="criteria" data-is-stacked="0">
This seems to be causing the issue, but I have no idea how to fix it. Any ideas?
##Analyzing admin JS in console##
SelectBox.js loads in the console and declares var = SelectBox = {... which is never called.
SelectFilter.js also loads but the function is never called:
/*
SelectFilter2 - Turns a multiple-select box into a filter interface.
Requires core.js, SelectBox.js and addevent.js.
*/
(function($) {
function findForm(node) {
// returns the node of the form containing the given node
if (node.tagName.toLowerCase() != 'form') {
return findForm(node.parentNode);
}
return node;
}
window.SelectFilter = {
init: function(field_id, field_name, is_stacked, admin_static_prefix) {
if (field_id.match(/__prefix__/)){
// Don't intialize on empty forms.
return;
}
var from_box = document.getElementById(field_id);
from_box.id += '_from'; // change its ID
from_box.className = 'filtered';
var ps = from_box.parentNode.getElementsByTagName('p');
for (var i=0; i<ps.length; i++) {
if (ps[i].className.indexOf("info") != -1) {
// Remove <p class="info">, because it just gets in the way.
from_box.parentNode.removeChild(ps[i]);
} else if (ps[i].className.indexOf("help") != -1) {
// Move help text up to the top so it isn't below the select
// boxes or wrapped off on the side to the right of the add
// button:
from_box.parentNode.insertBefore(ps[i], from_box.parentNode.firstChild);
}
}
// <div class="selector"> or <div class="selector stacked">
var selector_div = quickElement('div', from_box.parentNode);
selector_div.className = is_stacked ? 'selector stacked' : 'selector';
// <div class="selector-available">
var selector_available = quickElement('div', selector_div, '');
selector_available.className = 'selector-available';
var title_available = quickElement('h2', selector_available, interpolate(gettext('Available %s') + ' ', [field_name]));
quickElement('img', title_available, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of available %s. You may choose some by selecting them in the box below and then clicking the "Choose" arrow between the two boxes.'), [field_name]));
var filter_p = quickElement('p', selector_available, '', 'id', field_id + '_filter');
filter_p.className = 'selector-filter';
var search_filter_label = quickElement('label', filter_p, '', 'for', field_id + "_input");
var search_selector_img = quickElement('img', search_filter_label, '', 'src', admin_static_prefix + 'img/selector-search.gif', 'class', 'help-tooltip', 'alt', '', 'title', interpolate(gettext("Type into this box to filter down the list of available %s."), [field_name]));
filter_p.appendChild(document.createTextNode(' '));
var filter_input = quickElement('input', filter_p, '', 'type', 'text', 'placeholder', gettext("Filter"));
filter_input.id = field_id + '_input';
selector_available.appendChild(from_box);
var choose_all = quickElement('a', selector_available, gettext('Choose all'), 'title', interpolate(gettext('Click to choose all %s at once.'), [field_name]), 'href', 'javascript: (function(){ SelectBox.move_all("' + field_id + '_from", "' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_all_link');
choose_all.className = 'selector-chooseall';
// <ul class="selector-chooser">
var selector_chooser = quickElement('ul', selector_div, '');
selector_chooser.className = 'selector-chooser';
var add_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Choose'), 'title', gettext('Choose'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_from","' + field_id + '_to"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_add_link');
add_link.className = 'selector-add';
var remove_link = quickElement('a', quickElement('li', selector_chooser, ''), gettext('Remove'), 'title', gettext('Remove'), 'href', 'javascript: (function(){ SelectBox.move("' + field_id + '_to","' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_link');
remove_link.className = 'selector-remove';
// <div class="selector-chosen">
var selector_chosen = quickElement('div', selector_div, '');
selector_chosen.className = 'selector-chosen';
var title_chosen = quickElement('h2', selector_chosen, interpolate(gettext('Chosen %s') + ' ', [field_name]));
quickElement('img', title_chosen, '', 'src', admin_static_prefix + 'img/icon-unknown.gif', 'width', '10', 'height', '10', 'class', 'help help-tooltip', 'title', interpolate(gettext('This is the list of chosen %s. You may remove some by selecting them in the box below and then clicking the "Remove" arrow between the two boxes.'), [field_name]));
var to_box = quickElement('select', selector_chosen, '', 'id', field_id + '_to', 'multiple', 'multiple', 'size', from_box.size, 'name', from_box.getAttribute('name'));
to_box.className = 'filtered';
var clear_all = quickElement('a', selector_chosen, gettext('Remove all'), 'title', interpolate(gettext('Click to remove all chosen %s at once.'), [field_name]), 'href', 'javascript: (function() { SelectBox.move_all("' + field_id + '_to", "' + field_id + '_from"); SelectFilter.refresh_icons("' + field_id + '");})()', 'id', field_id + '_remove_all_link');
clear_all.className = 'selector-clearall';
from_box.setAttribute('name', from_box.getAttribute('name') + '_old');
// Set up the JavaScript event handlers for the select box filter interface
addEvent(filter_input, 'keyup', function(e) { SelectFilter.filter_key_up(e, field_id); });
addEvent(filter_input, 'keydown', function(e) { SelectFilter.filter_key_down(e, field_id); });
addEvent(from_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
addEvent(to_box, 'change', function(e) { SelectFilter.refresh_icons(field_id) });
addEvent(from_box, 'dblclick', function() { SelectBox.move(field_id + '_from', field_id + '_to'); SelectFilter.refresh_icons(field_id); });
addEvent(to_box, 'dblclick', function() { SelectBox.move(field_id + '_to', field_id + '_from'); SelectFilter.refresh_icons(field_id); });
addEvent(findForm(from_box), 'submit', function() { SelectBox.select_all(field_id + '_to'); });
SelectBox.init(field_id + '_from');
SelectBox.init(field_id + '_to');
// Move selected from_box options to to_box
SelectBox.move(field_id + '_from', field_id + '_to');
if (!is_stacked) {
// In horizontal mode, give the same height to the two boxes.
var j_from_box = $(from_box);
var j_to_box = $(to_box);
var resize_filters = function() { j_to_box.height($(filter_p).outerHeight() + j_from_box.outerHeight()); }
if (j_from_box.outerHeight() > 0) {
resize_filters(); // This fieldset is already open. Resize now.
} else {
// This fieldset is probably collapsed. Wait for its 'show' event.
j_to_box.closest('fieldset').one('show.fieldset', resize_filters);
}
}
// Initial icon refresh
SelectFilter.refresh_icons(field_id);
},
refresh_icons: function(field_id) {
var from = $('#' + field_id + '_from');
var to = $('#' + field_id + '_to');
var is_from_selected = from.find('option:selected').length > 0;
var is_to_selected = to.find('option:selected').length > 0;
// Active if at least one item is selected
$('#' + field_id + '_add_link').toggleClass('active', is_from_selected);
$('#' + field_id + '_remove_link').toggleClass('active', is_to_selected);
// Active if the corresponding box isn't empty
$('#' + field_id + '_add_all_link').toggleClass('active', from.find('option').length > 0);
$('#' + field_id + '_remove_all_link').toggleClass('active', to.find('option').length > 0);
},
filter_key_up: function(event, field_id) {
var from = document.getElementById(field_id + '_from');
// don't submit form if user pressed Enter
if ((event.which && event.which == 13) || (event.keyCode && event.keyCode == 13)) {
from.selectedIndex = 0;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = 0;
return false;
}
var temp = from.selectedIndex;
SelectBox.filter(field_id + '_from', document.getElementById(field_id + '_input').value);
from.selectedIndex = temp;
return true;
},
filter_key_down: function(event, field_id) {
var from = document.getElementById(field_id + '_from');
// right arrow -- move across
if ((event.which && event.which == 39) || (event.keyCode && event.keyCode == 39)) {
var old_index = from.selectedIndex;
SelectBox.move(field_id + '_from', field_id + '_to');
from.selectedIndex = (old_index == from.length) ? from.length - 1 : old_index;
return false;
}
// down arrow -- wrap around
if ((event.which && event.which == 40) || (event.keyCode && event.keyCode == 40)) {
from.selectedIndex = (from.length == from.selectedIndex + 1) ? 0 : from.selectedIndex + 1;
}
// up arrow -- wrap around
if ((event.which && event.which == 38) || (event.keyCode && event.keyCode == 38)) {
from.selectedIndex = (from.selectedIndex == 0) ? from.length - 1 : from.selectedIndex - 1;
}
return true;
}
}
})(django.jQuery);
##INSTALLED APPS##
########## APP CONFIGURATION
INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
DJANGO_APPS = (
'django_gulp',
# Default Django apps:
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Useful template tags:
# 'django.contrib.humanize',
# Python-Social-Auth
'social_django',
# Admin panel and documentation:
'django.contrib.admin',
'django.core.management',
# 'django.contrib.admindocs',
# for task queue
# For django-storages static store on AWS S3
'storages',
# Other django apps
'rest_framework',
)
# Apps specific for this project go here.
LOCAL_APPS = (
'base',
'myproject',
'users',
'campaigns',
)
I copied the static/admin files from Django.contrib using the following command in terminal:
cp -a /Users/username/.virtualenvs/rs/lib/python3.9/site-packages/django/contrib/admin/. /Users/username/Documents/myproject/static/
I then ran collectstatic to upload the files to S3, where they are stored for production. This actually worked, but seems a bit hacky. I must not be doing something right for these files to not update upon Django upgrade.

Unable to save new "ActiveRecord" in database through Codeception

I have a simple Unit test in which I'm trying to create a new record in "orders" table . So when the test is run it throws an exception :
[yii\db\IntegrityException] SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'created_by' cannot be null
I guess this is due to the so called "BlamemableBehavior" trying to update the "created_by" column . So I tried to detach it and to manually pass the "created_by" value . Neither of both worked . Can you please help ?
<?php
namespace frontend\tests\unit\models;
// use common\fixtures\UserFixture;
use frontend\components\Payments;
use common\models\order\Order;
class RandomTest extends \Codeception\Test\Unit
{
/**
* #var \frontend\tests\UnitTester
*/
protected $tester;
protected $order;
public function _before(){
//this doesn't work
$this->order = $this->tester->haveRecord(Order::class,
[
'id' => 577,
'payment_type' => 4,
'status' => 1,
'amount' => 1,
'created_by' => 561,
'updated_by' => 561,
]);
}
public function testRandom(){
//this does not work either
/*$model = new Order;
$model->detachBehavior('BlameableBehavior');
$model->payment_type = 4;
$model->status = 1;
$model->amount = 1;
$model->created_by = 561;
$model->updated_by = 561;
$model->save();*/
}
}
The "Order" model :
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors[] = [
'class' => \common\components\behaviors\modelLog\ActiveRecordHistoryBehavior::className(),
'manager' => '\common\components\behaviors\modelLog\managers\DBManager',
'ignoreFields' => ['updated_at', 'created_at']
];
return $behaviors;
}
/**
* #inheritdoc
*/
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['paid'] = [
'status', 'invoice_number', 'paid_date', 'is_used', 'allocation'];
return $scenarios;
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['status', 'payment_type', 'invoice_reference', 'invoice_number'], 'integer'],
[['payment_type', 'amount', 'vat', 'total_amount', 'credit', 'invoice_reference'], 'required'],
[['amount', 'vat', 'total_amount', 'credit', 'discount'], 'number'],
[['reason'], 'trim'],
[['allocation'], 'string']
];
}

how can i fetch database records between two dates in drupal

I need to fetch the table records from start_date to end_date ,i tried some queries but its showing all records
class uniview8PastpaymentController extends ControllerBase {
public static function load($entry = array()) {
$maxDate = \Drupal::request()->query->get('set_start_date');
drupal_set_message(t("$maxDate"));
$minDate = \Drupal::request()->query->get('set_end_date');
drupal_set_message(t("$minDate"));
$select = db_select('pastpayments', 'example');
$select->fields('example');
// Add each field and value as a condition to this query.
foreach ($entry as $field => $value) {
$query->condition('paiddate', array($maxDate, $minDate), 'BETWEEN');
}
// Return the result in object format.
return $select->execute()->fetchAll();
}
Example
$start_time = '1493029282.696';
$end_time = '1493029283.304';
$query = \Drupal::database()->select('cache_data', 'nfd');
$query->fields('nfd', ['cid', 'data', 'expire']);
$query->condition('created', array($start_time, $end_time), 'BETWEEN');
$result = $query->execute();

Django Admin: populate the field based on previous field value

I have a model in django admin as follows
ChoiceA= (
("on-false","on-false"),
("on-true","on-true"),
)
ChoiceB = (
("always","always"),
("never","never"),
)
id = models.CharField(verbose_name="Field",max_length=32)
type = models.CharField(verbose_name="Expression",max_length=32)
action = models.CharField(max_length=32, choices=x)
Now based on the type entered by the user ie if user enters type = "a" then action's choices should be set to ChoiceA and if user enters type ="b" then action's choices should be set to ChoiceB. How can I achieve this in Django Admin?
Edit:
action_change.js
jQuery(document).ready(function(){
$("#id_type").change( function(event) {
$.ajax({
"type" : "POST",
"url" : "/action_choices/",
"dataType" : "json",
"cache" : false,
"error" : alert("hello"),
"success" : function(json) {
$('#id_action >option').remove();
for(var j = 0; j < json.length; j++){
$('#id_action').append($('<option></option>').val(json[j][0]).html(json[j][1]));
}
}
});
});
});
You can achieve it using Ajax and jQuery:
models.py:
type = models.CharField(verbose_name="Expression",max_length=32)
action = models.CharField(max_length=32, choices = (('', ''), ))
admin.py:
class MyModelAdmin(admin.ModelAdmin):
list_display = ('type', )
class Media:
js = ['/static/js/action_change.js']
admin.site.register(MyModel, MyModelAdmin)
urls.py:
url(r'^action_choices/', 'myproject.myapp.views.action_choices'),
views.py:
def action_choices(request):
action_list = []
ChoiceA = ("on-false", "on-true")
ChoiceB = ("always", "never")
action_type = request.GET.get('action_type')
if str(action_type).lower() == 'a':
choices = ChoiceA
elif str(action_type).lower() == 'b':
choices = ChoiceB
else:
choices = ()
[action_list.append((each,each)) for each in choices]
json = simplejson.dumps(action_list)
return HttpResponse(json, mimetype='application/javascript')
Create the file action_change.js with following content in your static folder and define correct path in class Media of ModelAdmin.
action_change.js
(function($){
$(function(){
$(document).ready(function() {
$('#id_type').bind('keyup', type_change);
$('#id_action >option').show();
});
});
})(django.jQuery);
// based on the type, action will be loaded
var $ = django.jQuery.noConflict();
function type_change()
{
var action_type = $('#id_type').val();
$.ajax({
"type" : "GET",
"url" : "/action_choices/?action_type="+action_type,
"dataType" : "json",
"cache" : false,
"success" : function(json) {
$('#id_action >option').remove();
for(var j = 0; j < json.length; j++){
$('#id_action').append($('<option></option>').val(json[j][0]).html(json[j][1]));
}
}
})(jQuery);
}
This should work fine for the scenario you asked. And I'm giving my suggestion below:
models.py
type = models.CharField(verbose_name="Expression",max_length=32, choices = (('a', 'a'), ('b', 'b'), ))
action = models.CharField(max_length=32, choices = (('', ''), ))
action_change.js (line 5)
$('#id_type').bind('change', type_change);
You would have to initialize the action field with all possible choices, or Django will complain that a choice that didn't previously exist isn't a valid choice.
My recommendation would be to initialize the field with all of the possible choices, and use JavaScript to toggle the visibility of the choices, depending on the value of type. There are a few plugins around that will handle dynamic fields in Django admin, but most that I've seen deal with ForeignKey or ManyToMany fields that need to do lookups.
You're probably best off just adding some JavaScript to your admin form via the Media meta class and handling it yourself.