I would like to use tom-select as an Django autocomplete select-widget.
In the corresponding database table (Runners) are several hundred rows, so that I can't load all into the html page.
In the model RaceTeam I have a ForeignKey to Runner.
The select widget will be used in whole pages and in html fragments (via htmx).
I tried subclass django.forms.Select, but this sucks all db rows into widget.choices.
I use this solution:
I copy the tom-select JS+CSS to static/relayrace/vendor/.
class RunnerSelectWidget(Select):
class Media:
css = {
'all': ('relayrace/vendor/tom-select.css',)
}
js = ('relayrace/vendor/tom-select.complete.min.js',)
def set_choices(self, value):
# there can be several thousand choices. We don't want them here
self._choices = []
def get_choices(self):
choices = list(self._choices)
return choices
choices = property(get_choices, set_choices)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['widget']['attrs']['class']='runner-select'
return context
def optgroups(self, name, value, attrs=None):
# Make the initial value available
self._choices = [(id, runner_to_select_text(Runner.objects.get(pk=id))) for id in value]
return super().optgroups(name, value, attrs)
def runner_select_json(request):
# You might want to add some permission checking here. Otherwise everybody
# can get data via this endpoint.
query = request.GET.get('q', '').strip()
qs = get_queryset(query)
items = [dict(value=r.pk, text=runner_to_select_text(r)) for r in qs]
return JsonResponse(dict(items=items, total_count=len(items)))
def runner_to_select_text(runner):
return f'{runner.user.first_name} {runner.user.last_name}'
def get_queryset(query):
if not query:
return Runner.objects.none()
return Runner.objects.filter(Q(user__username__icontains=query)|
Q(user__first_name__icontains=query)|
Q(user__last_name__icontains=query)).order_by('user__username')
Usage of the widget:
class RaceTeamForm(forms.ModelForm):
class Meta:
model = RaceTeam
widgets = {
'leader': RunnerSelectWidget(),
}
JS to init the widget on whole page load and on fragment load:
htmx.onLoad(function(elt) {
var allSelects = htmx.findAll(elt, ".runner-select")
for( select of allSelects ) {
new TomSelect(select, {
// fetch remote data
load: function(query, callback) {
var url = '/runner_select_json?q=' + encodeURIComponent(query);
fetch(url)
.then(response => response.json())
.then(json => {
callback(json.items);
}).catch(()=>{
callback();
});
}
})}});
urls.py
url('runner_select_json', runner_select_json, name='runner_select_json')
Related
Here is my code. I want to change some fields background color according to the result of a rpc call. But the change only happens when I switch the mode between edit and save.It should be changed when I open this record.
Any idea?
var render = listRender.extend({
init: function () {
this._super.apply(this, arguments)
},
async _render() {
const result = this._super.apply(this, arguments);
var fields = [];
for (let index in this.arch.children) {
if (this.arch.children[index].tag === "field") {
fields.push({"field_name": this.arch.children[index].attrs.name, "index": index});
}
}
var self = this;
this.arch.children[36]["attrs"]["class"] = "red_color";
var infos = await this._rpc({
model: "purchase.order",
method: "is_updated",
args: [{"fields": fields, "id": this.state.res_ids}]
});
infos.data.forEach(ele => {
this.arch.children[parseInt(ele["index"])]["attrs"]["class"] = "red_color";
})
},
});
Render overriding is not the right way to deal with your problem. Consider changing the corresponding view : "sale.order.form" instead.
You can find it this way : in App Settings, switch to debug mode by adding "?debug=1" in your url, reload the page, then go to the last item in the top menu bar: Technical > Views.
and then, search for : sale.order.form
...which corresponds to the following xml files in src/odoo/addons : sale/sale_views.xml or sale_purchase/sale_views.xml and/or website_sale/sale_order_views.xml
The corresponding url must be similar too (but with different id=...):
https://yourodoodomain.com/web#id=949&action=28&model=ir.ui.view&view_type=form&cids=1&menu_id=4
I'm using DRF as my backend API in conjunction with React.
I am making a GET request using fetch and then populating a react table with the data provided by DRF.
The issue is that when I first load the page or when I manually refresh it, theres a significant delay for the data to be fetched. I've tried limiting to 5 items but it still always takes 2 seconds.
However, if I make the same request after the page has fully loaded the speed is normal. I've added a gif to illustrate in case I didn't explain it properly. Once the role is updated in the DB, the same GET request is done again to repopulate the table.
My views, models and serializers:
#api_view(['GET'])
def getUserData(request):
items = User.objects.all()
# enabling pagination because api_view doesn't have it by default
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(items, request)
# creating 1 serializer instance for paginated and 1 for full
serializer_page = UserSerializer(result_page, many=True)
serializer_full = UserSerializer(items, many=True)
# if limit is not specified, return all object data
if 'limit' not in request.query_params:
return Response(serializer_full.data)
else:
return Response(serializer_page.data)
class Role(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='roles')
assigned_role = models.CharField(max_length=100)
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
fields = ['user', 'assigned_role']
class UserSerializer(serializers.ModelSerializer):
roles = RoleSerializer(read_only=True, many=False)
class Meta:
model = User
fields = '__all__'
Javascript code in case it's relevant:
useEffect(() => {
const fetchUsers = (num) => {
fetch(`http://127.0.0.1:8000/api/users/?limit=${num}`)
.then(response => response.json())
.then(data => {
console.log('Data loaded in useEffect')
dataObj = data
setUsers(dataObj)
setLoading(true)
})
}
fetchUsers(20)
}
, [searchDone])
To optimize the response time of our web application I would like to load View Components in .NET Core 2.2 asynchronous from client side without Javascript. Of course you could achieve this by loading them with an AJAX Call and then render the view from a Controller.
But I would like to keep the intellisense and work with tags in the code, so you can invoke a view component with a tag like this multiple times in different places and don't need to create multiple ajax posts in JS:
<vc:free-text ftx-code="STARTPAGE" obj-ref="null" custom-para="null"></vc:free-text>
I tried to load the view component via TagHelper, but apparently they also are rendered synchronously (only server side asynchronous):
[HtmlTargetElement("widget", Attributes = WidgetNameAttributeName)]
public class WidgetTagHelper : TagHelper
{
private readonly IViewComponentHelper _viewComponentHelper;
public WidgetTagHelper(IViewComponentHelper viewComponentHelper)
{
_viewComponentHelper = viewComponentHelper;
}
.....
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
((IViewContextAware)_viewComponentHelper).Contextualize(ViewContext);
var content = await _viewComponentHelper.InvokeAsync(typeof(FreeTextViewComponent), new { ftxCode = FTXCode, objRef = OBJRef, customPara = CustomPara });
output.Content.SetHtmlContent(content);
}
}
Does anyone know an approach (wrapper, TagHelper, etc.?) where I first load the ViewComponent with an "empty" modal and when it's rendered it makes an client side asynchronous callback to fetch the actual data, like described in this post?
https://github.com/aspnet/AspNetCore.Docs/issues/7914#issuecomment-441280663
View components and tag helpers cannot be accessed directly. They aren't part of the request pipeline and don't respond to anything. The best you can do is create a view that happens to utilize the component or tag helper, and then return that view from an action, which is what you would actually make the request to.
Generally speaking, for this type of stuff, you'd be better served with a client-side library like Vue, Angular, etc. and create client-side components via those libraries. Then, your server acts as an API, returning JSON data that you feed into those components.
I ended up by reloading them via an AJAX call, but only one for all rendered views. First, I load the ViewComponent with a parameter "async" set to true (it only fetches data, when async is set to "false"):
public async Task<IViewComponentResult> InvokeAsync(string ftxCode, string objRef = "", List<FTXPara> customPara = null, bool async = false)
{
if (async)
{
ViewData["ftxCode"] = ftxCode;
ViewData["async"] = async;
return View(new GetFreeTextResponse());
}
List<CMailCustomParameter> para = new List<CMailCustomParameter>();
if (customPara != null)
{
foreach (var p in customPara)
{
para.Add(new CMailCustomParameter { ParameterName = p.Name, ParameterValue = p.Value });
}
}
var resp = await GetItemsAsync(ftxCode, objRef, para);
return View(resp);
}
private async Task<GetFreeTextResponse> GetItemsAsync(string ftxCode, string objRef, List<CMailCustomParameter> para)
{
return await FreeTextService.GetFreeText(new GetFreeTextRequest { FTX_Code = ftxCode, ObjRef = objRef, CustomParameters = para });
}
Then I loop through every rendered "empty" View Component and load the View Component with the actual data from a controller (this time "async" is false"):
$('.view-component').each(function (i, obj) {
var that = this;
var id = $(this).attr('id');
var test = $(this).data();
console.log($(this).data('async'));
if ($(this).data('async')) {
$.ajax({
url: "/VC/Get" + "FreeText" + "ViewComponent",
type: 'POST',
data: $(this).data(),
contentType: 'application/x-www-form-urlencoded',
success: function (data) {
$(that).html(data);
},
error: function (xhr, status, error) {
console.log(xhr.status + " - " + error);
}
});
}
});
I have an Index.cshtml razor view that lists NewsFeedPosts on it.
In the controller when in the Index() Action Method I am calling my repository initially getting all of the NewsFeedPosts along with their Comments like this:
var newsFeedPosts = _context.NewsFeedPosts
.Include(p => p.Comments)
.OrderBy(t => t.CreatedDate)
.ToList();
In the actual view I am doing something like this to only show the first 4:
<div id="comments">
#foreach (var comment in post.Comments.Take(4))
{
...
}
</div>
Then I will have a link that says 'View additional comments', kind of like how StackOverflow does it.
On Click of this link:
I know I can get this does with an Ajax request calling an action Method called GetNewsFeedPostComments and simply loop through them and append to the #comments div.
However, since I am already retrieving all of the Comments on page load (as shown in the below code) is this really the best way to do this? I feel like there is a better way since I already have all Comments for each news feed post inside of my ViewModel already.
Here is what my pages ViewModel looks like:
public class NewsFeedPostIndexViewModel
{
public IEnumerable<NewsFeedPostViewModel> NewsFeedPosts { get; set; }
}
and when the page initially loads here is how I am populating this ViewModel and sending it to the View:
// GET: NewsFeedPosts
public IActionResult Index()
{
// Get a list of all NewsFeedPosts including comments
var newsFeedPosts = _repository.GetAllNewsFeedPosts();
var newsFeedPostViewModels = newsFeedPosts.Select(fp => new NewsFeedPostViewModel()
{
Id = fp.Id,
Title = fp.Title,
Content = WebUtility.HtmlDecode(fp.Content),
Comments = fp.Comments
}).ToList();
NewsFeedPostIndexViewModel vm = new NewsFeedPostIndexViewModel()
{
NewsFeedPosts = newsFeedPostViewModels
};
return View(vm);
}
So in this JavaScript function is there anyway to utilize what I already have bound in the ViewModel?
$(".lnkViewAdditionalComments").click(function (e) {
e.preventDefault(e);
var newsFeedPostId = $(this).attr('data-newsFeedPost-Id');
// retrieve remaining comments by using skip(4) and loop through them
var comments = #Html.Raw(Model.NewsFeedPostIndexViewModel.Where(f => f.Id = // need to pass JS string here: feedPostId)));
// foreach $(#comments).append(".....");
});
why don't you use grep function in Js
#{
var GetAllComments = Model.FeedPosts.Select(x => x.Comments);
}
<script>
var feedPostId = 1;
var GetCommentJs =#(Html.Raw(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(GetAllComments)));
var filterFata = $.grep(GetCommentJs, function (value, index) {
return value.FeedPostId == feedPostId;
});
</script>
NOTE: you can use also slice function to take limited number of
records after grep.
I have a Contract class where contract_mod allows to extend a contract from a previous one. contract_mod should only show contracts related with the person that we selected previously.
The Contract class returns the field person. As I've never work with AJAX/jQuery I don't know where to start.
So, my goal is that the field contract_mod depends on hte field person (using the Admin interface).
class Contract(models.Model):
person = models.ForeignKey(Person) #person hired
contract_mod = models.OneToOneField('self', blank = True, null = True) #allows to extend a contract
...
As the field contract_mod is OneToOneField I can't use django-smart-selects or django-ajax-select
In a similar situation I did the following (now adapted to your model):
models
class Person(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
def get_name(self):
return self.name
class Contract(models.Model):
person = models.ForeignKey(Person) #person hired
contract_mod = models.OneToOneField('self', blank = True, null = True)
contract_name = models.CharField(max_length=20) #just for testing
def __unicode__(self):
return self.get_name() + " " +self.contract_name
def get_name(self):
return self.person.get_name() #to make sure you get the person name in the admin
def contract_mod_name(self):
if self.contract_mod:
return self.contract_mod.contract_name
else:
return ""
admin
class SelectField(forms.ChoiceField):
def clean(self, value):
return value
class CForm(forms.ModelForm):
contracts_from_selected = SelectField()
class Meta:
model = Contract
widgets = { 'contract_mod' : forms.widgets.Select(attrs={'hidden' : 'true'}) }
class CAdmin(admin.ModelAdmin):
form = CForm
list_display = ('contract_name','get_name','contract_mod_name')#what you like
def save_model(self, request, obj, form, change):
if request.POST.get('contracts_from_selected'):
obj.contract_mod=Contract.objects.get(id=int(request.POST.get('contracts_from_selected')))
obj.save()
Override the change_form.html template (by copying it from the django/contrib/admin/templates/admin into your yourapp/templates/admin/yourapp directory) and add the following Javascript:
$(function () {
$("#id_person").change(function () {
var options = $("#id_contract_mod option").filter(function () {
return $(this).html().split(" ")[0] === $("#id_person option:selected").html();
}).clone();
$("#id_contracts_from_selected").empty();
$("#id_contracts_from_selected").append(options);
});
});
One shortcoming is, that this uses the visible html entry to store the person - contract relation. So the person is also visible in the dropdown. To avoid this you could add an attribute to the options instead - see here:
Django form field choices, adding an attribute
Yes and it would be good to completely hide the contract_mod ChoiceField. Via the hidden=True in the widget only the dropdown is hidden.